diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml
index e3b2b9e..04b9508 100644
--- a/core/config/schema/core.data_types.schema.yml
+++ b/core/config/schema/core.data_types.schema.yml
@@ -289,6 +289,12 @@ block_settings:
     view_mode:
       type: string
       label: 'View mode'
+    visibility:
+      type: sequence
+      label: 'Visibility Conditions'
+      sequence:
+        - type: condition.plugin.[id]
+          label: 'Visibility Condition'
     provider:
       type: string
       label: 'Provider'
@@ -303,3 +309,11 @@ condition.plugin:
     negate:
       type: boolean
       label: 'Negate'
+    uuid:
+      type: string
+      label: 'UUID'
+    context_mapping:
+      type: sequence
+      label: 'Context assignments'
+      sequence:
+        - type: string
diff --git a/core/lib/Drupal/Core/Condition/ConditionAccessResolverTrait.php b/core/lib/Drupal/Core/Condition/ConditionAccessResolverTrait.php
new file mode 100644
index 0000000..35587eb
--- /dev/null
+++ b/core/lib/Drupal/Core/Condition/ConditionAccessResolverTrait.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Condition\ConditionAccessResolverTrait.
+ */
+
+namespace Drupal\Core\Condition;
+
+use Drupal\Component\Plugin\Exception\PluginException;
+
+/**
+ * Resolves a set of conditions.
+ */
+trait ConditionAccessResolverTrait {
+
+  /**
+   * 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'.
+   *
+   * @return bool
+   *   Whether these conditions grant or deny access.
+   */
+  protected function resolveConditions($conditions, $condition_logic) {
+    foreach ($conditions as $condition) {
+      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 needed, deny access.
+      if (!$pass && $condition_logic == 'and') {
+        return FALSE;
+      }
+      // If a condition passes and only one condition was needed, grant access.
+      elseif ($pass && $condition_logic == 'or') {
+        return TRUE;
+      }
+    }
+
+    // Return TRUE if logic was 'and', meaning all rules passed.
+    // Return FALSE if logic was 'or', meaning no rule passed.
+    return $condition_logic == 'and';
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Condition/ConditionPluginBag.php b/core/lib/Drupal/Core/Condition/ConditionPluginBag.php
new file mode 100644
index 0000000..394bebf
--- /dev/null
+++ b/core/lib/Drupal/Core/Condition/ConditionPluginBag.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Condition\ConditionPluginBag.
+ */
+
+namespace Drupal\Core\Condition;
+
+use Drupal\Component\Plugin\Context\ContextInterface;
+use Drupal\Core\Plugin\DefaultPluginBag;
+
+/**
+ * Provides a collection of condition plugins.
+ */
+class ConditionPluginBag extends DefaultPluginBag {
+
+  /**
+   * An array of collected contexts for conditions.
+   *
+   * @var \Drupal\Component\Plugin\Context\ContextInterface[]
+   */
+  protected $conditionContexts = array();
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return \Drupal\Core\Condition\ConditionInterface
+   */
+  public function &get($instance_id) {
+    return parent::get($instance_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfiguration() {
+    $configuration = parent::getConfiguration();
+    // Remove configuration if it matches the defaults.
+    foreach ($configuration as $instance_id => $instance_config) {
+      $default_config = array();
+      $default_config['id'] = $instance_id;
+      $default_config += $this->get($instance_id)->defaultConfiguration();
+      if ($default_config === $instance_config) {
+        unset($configuration[$instance_id]);
+      }
+    }
+    return $configuration;
+  }
+
+  /**
+   * Sets the condition context for a given name.
+   *
+   * @param string $name
+   *   The name of the context.
+   * @param \Drupal\Component\Plugin\Context\ContextInterface $context
+   *   The context to add.
+   *
+   * @return $this
+   */
+  public function addContext($name, ContextInterface $context) {
+    $this->conditionContexts[$name] = $context;
+    return $this;
+  }
+
+  /**
+   * Gets the values for all defined contexts.
+   *
+   * @return \Drupal\Component\Plugin\Context\ContextInterface[]
+   *   An array of set contexts, keyed by context name.
+   */
+  public function getConditionContexts() {
+    return $this->conditionContexts;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Display/VariantBase.php b/core/lib/Drupal/Core/Display/VariantBase.php
new file mode 100644
index 0000000..a30602f
--- /dev/null
+++ b/core/lib/Drupal/Core/Display/VariantBase.php
@@ -0,0 +1,230 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Display\VariantBase.
+ */
+
+namespace Drupal\Core\Display;
+
+use Drupal\Core\Condition\ConditionAccessResolverTrait;
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\Core\Plugin\PluginDependencyTrait;
+use Drupal\block\Plugin\BlocksPluginBag;
+
+/**
+ * Provides a base class for PageVariant plugins.
+ */
+abstract class VariantBase extends PluginBase implements VariantInterface {
+
+  use PluginDependencyTrait;
+
+  /**
+   * The plugin bag that holds the block plugins.
+   *
+   * @var \Drupal\block\Plugin\BlocksPluginBag
+   */
+  protected $blockPluginBag;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $this->setConfiguration($configuration);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getBlockBag() {
+    if (!$this->blockPluginBag) {
+      $this->blockPluginBag = new BlocksPluginBag(\Drupal::service('plugin.manager.block'), $this->configuration['blocks']);
+    }
+    return $this->blockPluginBag;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function label() {
+    return $this->configuration['label'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function adminLabel() {
+    return $this->pluginDefinition['admin_label'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function id() {
+    return $this->configuration['uuid'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWeight() {
+    return (int) $this->configuration['weight'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setWeight($weight) {
+    $this->configuration['weight'] = (int) $weight;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfiguration() {
+    return array(
+      'id' => $this->getPluginId(),
+      'blocks' => $this->getBlockBag()->getConfiguration(),
+    ) + $this->configuration;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setConfiguration(array $configuration) {
+    $this->configuration = $configuration + $this->defaultConfiguration();
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return array(
+      'label' => '',
+      'uuid' => '',
+      'weight' => 0,
+      'blocks' => array(),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    return $this->dependencies;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, array &$form_state) {
+    $form['label'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Label'),
+      '#description' => $this->t('The label for this page variant.'),
+      '#default_value' => $this->label(),
+      '#maxlength' => '255',
+    );
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, array &$form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitConfigurationForm(array &$form, array &$form_state) {
+    $this->configuration['label'] = $form_state['values']['label'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBlock($block_id) {
+    return $this->getBlockBag()->get($block_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addBlock(array $configuration) {
+    $configuration['uuid'] = $this->uuidGenerator()->generate();
+    $this->getBlockBag()->addInstanceId($configuration['uuid'], $configuration);
+    return $configuration['uuid'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeBlock($block_id) {
+    $this->getBlockBag()->removeInstanceId($block_id);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function updateBlock($block_id, array $configuration) {
+    $existing_configuration = $this->getBlock($block_id)->getConfiguration();
+    $this->getBlockBag()->setInstanceConfiguration($block_id, $configuration + $existing_configuration);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRegionAssignment($block_id) {
+    $configuration = $this->getBlock($block_id)->getConfiguration();
+    return isset($configuration['region']) ? $configuration['region'] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRegionAssignments() {
+    // Build an array of the region names in the right order.
+    $empty = array_fill_keys(array_keys($this->getRegionNames()), array());
+    $full = $this->getBlockBag()->getAllByRegion();
+    // Merge it with the actual values to maintain the ordering.
+    return array_intersect_key(array_merge($empty, $full), $empty);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRegionName($region) {
+    $regions = $this->getRegionNames();
+    return isset($regions[$region]) ? $regions[$region] : '';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBlockCount() {
+    return count($this->configuration['blocks']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access() {
+    return TRUE;
+  }
+
+  /**
+   * Returns the UUID generator.
+   *
+   * @return \Drupal\Component\Uuid\UuidInterface
+   */
+  protected function uuidGenerator() {
+    return \Drupal::service('uuid');
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Display/VariantInterface.php b/core/lib/Drupal/Core/Display/VariantInterface.php
new file mode 100644
index 0000000..4af77d9
--- /dev/null
+++ b/core/lib/Drupal/Core/Display/VariantInterface.php
@@ -0,0 +1,169 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Display\VariantInterface.
+ */
+
+namespace Drupal\Core\Display;
+
+use Drupal\Component\Plugin\ConfigurablePluginInterface;
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\Core\Plugin\PluginFormInterface;
+
+/**
+ * Provides an interface for PageVariant plugins.
+ */
+interface VariantInterface extends PluginInspectionInterface, ConfigurablePluginInterface, PluginFormInterface {
+
+  /**
+   * Returns the user-facing page variant label.
+   *
+   * @return string
+   *   The page variant label.
+   */
+  public function label();
+
+  /**
+   * Returns the admin-facing page variant label.
+   *
+   * This is for the type of page variant, not the configured variant itself.
+   *
+   * @return string
+   *   The page variant administrative label.
+   */
+  public function adminLabel();
+
+  /**
+   * Returns the unique ID for the page variant.
+   *
+   * @return string
+   *   The page variant ID.
+   */
+  public function id();
+
+  /**
+   * Returns the weight of the page variant.
+   *
+   * @return int
+   *   The page variant weight.
+   */
+  public function getWeight();
+
+  /**
+   * Sets the weight of the page variant.
+   *
+   * @param int $weight
+   *   The weight to set.
+   */
+  public function setWeight($weight);
+
+  /**
+   * Returns a specific block plugin.
+   *
+   * @param string $block_id
+   *   The block ID.
+   *
+   * @return \Drupal\block\BlockPluginInterface
+   *   The block plugin.
+   */
+  public function getBlock($block_id);
+
+  /**
+   * Adds a block to this page variant.
+   *
+   * @param array $configuration
+   *   An array of block configuration.
+   *
+   * @return string
+   *   The block ID.
+   */
+  public function addBlock(array $configuration);
+
+  /**
+   * Updates the configuration of a specific block plugin.
+   *
+   * @param string $block_id
+   *   The block ID.
+   * @param array $configuration
+   *   The array of configuration to set.
+   *
+   * @return $this
+   */
+  public function updateBlock($block_id, array $configuration);
+
+  /**
+   * Removes a specific block from this page variant.
+   *
+   * @param string $block_id
+   *   The block ID.
+   *
+   * @return $this
+   */
+  public function removeBlock($block_id);
+
+  /**
+   * Returns the region a specific block is assigned to.
+   *
+   * @param string $block_id
+   *   The block ID.
+   *
+   * @return string
+   *   The machine name of the region this block is assigned to.
+   */
+  public function getRegionAssignment($block_id);
+
+  /**
+   * Returns an array of regions and their block plugins.
+   *
+   * @return array
+   *   The array is first keyed by region machine name, with the values
+   *   containing an array keyed by block ID, with block plugin instances as the
+   *   values.
+   */
+  public function getRegionAssignments();
+
+  /**
+   * Returns the human-readable list of regions keyed by machine name.
+   *
+   * @return array
+   *   An array of human-readable region names keyed by machine name.
+   */
+  public function getRegionNames();
+
+  /**
+   * Returns the human-readable name of a specific region.
+   *
+   * @param string $region
+   *   The machine name of a region.
+   *
+   * @return string
+   *   The human-readable name of a region.
+   */
+  public function getRegionName($region);
+
+  /**
+   * Returns the number of blocks contained by the page variant.
+   *
+   * @return int
+   *   The number of blocks contained by the page variant.
+   */
+  public function getBlockCount();
+
+  /**
+   * Determines if this page variant is accessible.
+   *
+   * @return bool
+   *   TRUE if this page variant is accessible, FALSE otherwise.
+   */
+  public function access();
+
+  /**
+   * Returns the render array for the page variant.
+   *
+   * @return array
+   *   A render array for the page variant.
+   */
+  public function render();
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
index 68fbda3..672c613 100644
--- a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Plugin\Context;
 
+use Drupal\Component\Plugin\ConfigurablePluginInterface;
 use Drupal\Component\Plugin\Context\ContextInterface;
 use Drupal\Component\Plugin\ContextAwarePluginInterface;
 use Drupal\Component\Plugin\Exception\ContextException;
@@ -122,6 +123,12 @@ public function getMatchingContexts(array $contexts, DataDefinitionInterface $de
    * {@inheritdoc}
    */
   public function applyContextMapping(ContextAwarePluginInterface $plugin, $contexts, $mappings = array()) {
+    if ($plugin instanceof ConfigurablePluginInterface) {
+      $configuration = $plugin->getConfiguration();
+      if (isset($configuration['context_mapping'])) {
+        $mappings += array_flip($configuration['context_mapping']);
+      }
+    }
     $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.
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index 8c40c49..4f323e1 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -12,22 +12,6 @@
 use Symfony\Component\HttpFoundation\Request;
 
 /**
- * Shows this block on every page except the listed pages.
- */
-const BLOCK_VISIBILITY_NOTLISTED = 0;
-
-/**
- * Shows this block on only the listed pages.
- */
-const BLOCK_VISIBILITY_LISTED = 1;
-
-/**
- * Shows this block if the associated PHP code returns TRUE.
- */
-const BLOCK_VISIBILITY_PHP = 2;
-
-
-/**
  * Implements hook_help().
  */
 function block_help($route_name, Request $request) {
@@ -100,27 +84,15 @@ function block_page_build(&$page) {
   // The theme system might not yet be initialized. We need $theme.
   drupal_theme_initialize();
 
-  // Fetch a list of regions for the current theme.
-  $all_regions = system_region_list($theme);
   if (\Drupal::request()->attributes->get(RouteObjectInterface::ROUTE_NAME) != 'block.admin_demo') {
-    // Load all region content assigned via blocks.
-    foreach (array_keys($all_regions) as $region) {
-      // Assign blocks to region.
-      if ($blocks = block_get_blocks_by_region($region)) {
-        $page[$region] = $blocks;
-      }
-    }
-    // Once we've finished attaching all blocks to the page, clear the static
-    // cache to allow modules to alter the block list differently in different
-    // contexts. For example, any code that triggers hook_page_build() more
-    // than once in the same page request may need to alter the block list
-    // differently each time, so that only certain parts of the page are
-    // actually built. We do not clear the cache any earlier than this, though,
-    // because it is used each time block_get_blocks_by_region() gets called
-    // above.
-    drupal_static_reset('block_list');
+    $page += \Drupal::service('plugin.manager.page_variant')
+      ->createInstance('full_page')
+      ->setTheme($theme)
+      ->render();
   }
   else {
+    // Fetch a list of regions for the current theme.
+    $all_regions = system_region_list($theme);
     // Append region description if we are rendering the regions demo page.
     $visible_regions = array_keys(system_region_list($theme, REGIONS_VISIBLE));
     foreach ($visible_regions as $region) {
@@ -141,33 +113,6 @@ function block_page_build(&$page) {
 }
 
 /**
- * Gets a renderable array of a region containing all enabled blocks.
- *
- * @param $region
- *   The requested region.
- *
- * @return
- *   A renderable array of a region containing all enabled blocks.
- */
-function block_get_blocks_by_region($region) {
-  $build = array();
-  if ($list = block_list($region)) {
-    foreach ($list as $key => $block) {
-      if ($block->access('view')) {
-        $build[$key] = entity_view($block, 'block');
-      }
-    }
-    // If none of the blocks in this region are visible, then don't set anything
-    // else in the render array, because that would cause the region to show up.
-    if (!empty($build)) {
-      // block_list() already returned the blocks in sorted order.
-      $build['#sorted'] = TRUE;
-    }
-  }
-  return $build;
-}
-
-/**
  * Returns an array of block class instances by theme.
  *
  * @param $theme
@@ -258,43 +203,6 @@ function block_theme_initialize($theme) {
 }
 
 /**
- * Returns all blocks in the specified region for the current user.
- *
- * @param $region
- *   The name of a region.
- *
- * @return
- *   An array of block objects, indexed with the configuration object name
- *   that represents the configuration. If you are displaying your blocks in
- *   one or two sidebars, you may check whether this array is empty to see
- *   how many columns are going to be displayed.
- */
-function block_list($region) {
-  $blocks = &drupal_static(__FUNCTION__);
-
-  if (!isset($blocks)) {
-    global $theme;
-    $blocks = array();
-    foreach (entity_load_multiple_by_properties('block', array('theme' => $theme)) as $block_id => $block) {
-      // Onlye include valid blocks in the list.
-      // @todo Remove this check as part of https://drupal.org/node/1776830.
-      if ($block->getPlugin()) {
-        $blocks[$block->get('region')][$block_id] = $block;
-      }
-    }
-  }
-
-  // Create an empty array if there are no entries.
-  if (!isset($blocks[$region])) {
-    $blocks[$region] = array();
-  }
-
-  uasort($blocks[$region], 'Drupal\block\Entity\Block::sort');
-
-  return $blocks[$region];
-}
-
-/**
  * Implements hook_rebuild().
  */
 function block_rebuild() {
@@ -398,10 +306,11 @@ function template_preprocess_block(&$variables) {
  */
 function block_user_role_delete($role) {
   foreach (entity_load_multiple('block') as $block) {
-    $visibility = $block->get('visibility');
-    if (isset($visibility['roles']['roles'][$role->id()])) {
-      unset($visibility['roles']['roles'][$role->id()]);
-      $block->set('visibility', $visibility);
+    /** @var $block \Drupal\block\BlockInterface */
+    $visibility = $block->getVisibility();
+    if (isset($visibility['user_role']['roles'][$role->id()])) {
+      unset($visibility['user_role']['roles'][$role->id()]);
+      $block->getPlugin()->setVisibilityConfig('user_role', $visibility['user_role']);
       $block->save();
     }
   }
@@ -428,10 +337,11 @@ function block_menu_delete(Menu $menu) {
 function block_language_entity_delete(Language $language) {
   // Remove the block visibility settings for the deleted language.
   foreach (entity_load_multiple('block') as $block) {
-    $visibility = $block->get('visibility');
+    /** @var $block \Drupal\block\BlockInterface */
+    $visibility = $block->getVisibility();
     if (isset($visibility['language']['langcodes'][$language->id()])) {
       unset($visibility['language']['langcodes'][$language->id()]);
-      $block->set('visibility', $visibility);
+      $block->getPlugin()->setVisibilityConfig('language', $visibility['language']);
       $block->save();
     }
   }
diff --git a/core/modules/block/block.services.yml b/core/modules/block/block.services.yml
index bacc48e..5373645 100644
--- a/core/modules/block/block.services.yml
+++ b/core/modules/block/block.services.yml
@@ -6,3 +6,18 @@ services:
     class: Drupal\block\Theme\AdminDemoNegotiator
     tags:
       - { name: theme_negotiator, priority: 1000 }
+  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: ['@request_stack']
+    tags:
+      - { name: 'event_subscriber' }
diff --git a/core/modules/block/js/block.js b/core/modules/block/js/block.js
index 302c315..3affe12 100644
--- a/core/modules/block/js/block.js
+++ b/core/modules/block/js/block.js
@@ -26,10 +26,10 @@ function checkboxesSummary(context) {
         return vals.join(', ');
       }
 
-      $('#edit-visibility-node-type, #edit-visibility-language, #edit-visibility-role').drupalSetSummary(checkboxesSummary);
+      $('#edit-settings-visibility-node-type, #edit-settings-visibility-language, #edit-settings-visibility-user-role').drupalSetSummary(checkboxesSummary);
 
-      $('#edit-visibility-path').drupalSetSummary(function (context) {
-        var $pages = $(context).find('textarea[name="visibility[path][pages]"]');
+      $('#edit-settings-visibility-request-path').drupalSetSummary(function (context) {
+        var $pages = $(context).find('textarea[name="settings[visibility][request_path][pages]"]');
         if (!$pages.val()) {
           return Drupal.t('Not restricted');
         }
diff --git a/core/modules/block/src/BlockAccessController.php b/core/modules/block/src/BlockAccessController.php
index 3bd9fb3..0089470 100644
--- a/core/modules/block/src/BlockAccessController.php
+++ b/core/modules/block/src/BlockAccessController.php
@@ -8,53 +8,19 @@
 namespace Drupal\block;
 
 use Drupal\Core\Entity\EntityAccessController;
-use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Session\AccountInterface;
-use Drupal\Core\Path\AliasManagerInterface;
-use Drupal\Component\Utility\Unicode;
-use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Provides a Block access controller.
  */
-class BlockAccessController extends EntityAccessController implements EntityControllerInterface {
-
-  /**
-   * The node grant storage.
-   *
-   * @var \Drupal\Core\Path\AliasManagerInterface
-   */
-  protected $aliasManager;
-
-  /**
-   * Constructs a BlockAccessController object.
-   *
-   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
-   *   The entity type definition.
-   * @param \Drupal\Core\Path\AliasManagerInterface $alias_manager
-   *   The alias manager.
-   */
-  public function __construct(EntityTypeInterface $entity_type, AliasManagerInterface $alias_manager) {
-    parent::__construct($entity_type);
-    $this->aliasManager = $alias_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
-    return new static(
-      $entity_type,
-      $container->get('path.alias_manager')
-    );
-  }
+class BlockAccessController extends EntityAccessController {
 
   /**
    * {@inheritdoc}
    */
   protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
+    /** @var $entity \Drupal\block\BlockInterface */
     if ($operation != 'view') {
       return parent::checkAccess($entity, $operation, $langcode, $account);
     }
@@ -64,65 +30,8 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
       return FALSE;
     }
 
-    // User role access handling.
-    // If a block has no roles associated, it is displayed for every role.
-    // For blocks with roles associated, if none of the user's roles matches
-    // the settings from this block, access is denied.
-    $visibility = $entity->get('visibility');
-    if (!empty($visibility['role']['roles']) && !array_intersect(array_filter($visibility['role']['roles']), $account->getRoles())) {
-      // No match.
-      return FALSE;
-    }
-
-    // Page path handling.
-    // Limited visibility blocks must list at least one page.
-    if (!empty($visibility['path']['visibility']) && $visibility['path']['visibility'] == BLOCK_VISIBILITY_LISTED && empty($visibility['path']['pages'])) {
-      return FALSE;
-    }
-
-    // Match path if necessary.
-    if (!empty($visibility['path']['pages'])) {
-      // Assume there are no matches until one is found.
-      $page_match = FALSE;
-
-      // Convert path to lowercase. This allows comparison of the same path
-      // with different case. Ex: /Page, /page, /PAGE.
-      $pages = drupal_strtolower($visibility['path']['pages']);
-      if ($visibility['path']['visibility'] < BLOCK_VISIBILITY_PHP) {
-        // Compare the lowercase path alias (if any) and internal path.
-        $path = current_path();
-        $path_alias = Unicode::strtolower($this->aliasManager->getAliasByPath($path));
-        $page_match = drupal_match_path($path_alias, $pages) || (($path != $path_alias) && drupal_match_path($path, $pages));
-        // When $block->visibility has a value of 0
-        // (BLOCK_VISIBILITY_NOTLISTED), the block is displayed on all pages
-        // except those listed in $block->pages. When set to 1
-        // (BLOCK_VISIBILITY_LISTED), it is displayed only on those pages
-        // listed in $block->pages.
-        $page_match = !($visibility['path']['visibility'] xor $page_match);
-      }
-
-      // If there are page visibility restrictions and this page does not
-      // match, deny access.
-      if (!$page_match) {
-        return FALSE;
-      }
-    }
-
-    // Language visibility settings.
-    if (!empty($visibility['language']['langcodes']) && array_filter($visibility['language']['langcodes'])) {
-      if (empty($visibility['language']['langcodes'][\Drupal::languageManager()->getCurrentLanguage($visibility['language']['language_type'])->id])) {
-        return FALSE;
-      }
-    }
-
-    // If the plugin denies access, then deny access. Apply plugin access checks
-    // last, because it's almost certainly cheaper to first apply Block's own
-    // visibility checks.
-    if (!$entity->getPlugin()->access($account)) {
-      return FALSE;
-    }
-
-    return TRUE;
+    // Delegate to the plugin.
+    return $entity->getPlugin()->access($account);
   }
 
 }
diff --git a/core/modules/block/src/BlockBase.php b/core/modules/block/src/BlockBase.php
index 994b12e..7af838b 100644
--- a/core/modules/block/src/BlockBase.php
+++ b/core/modules/block/src/BlockBase.php
@@ -7,6 +7,11 @@
 
 namespace Drupal\block;
 
+use Drupal\block\Event\BlockConditionContextEvent;
+use Drupal\block\Event\BlockEvents;
+use Drupal\Component\Plugin\ContextAwarePluginInterface;
+use Drupal\Core\Condition\ConditionAccessResolverTrait;
+use Drupal\Core\Condition\ConditionPluginBag;
 use Drupal\Core\Plugin\ContextAwarePluginBase;
 use Drupal\block\BlockInterface;
 use Drupal\Component\Utility\Unicode;
@@ -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->getVisibilityConditions()->getConfiguration(),
+    ) + $this->configuration;
   }
 
   /**
@@ -75,6 +98,11 @@ public function setConfiguration(array $configuration) {
    *   An associative array with the default configuration.
    */
   protected function baseConfigurationDefaults() {
+    // @todo Allow list of conditions to be configured in
+    //   https://drupal.org/node/2284687.
+    $visibility = array_map(function ($definition) {
+      return array('id' => $definition['id']);
+    }, $this->conditionPluginManager()->getDefinitions());
     return array(
       'id' => $this->getPluginId(),
       'label' => '',
@@ -84,6 +112,7 @@ protected function baseConfigurationDefaults() {
         'max_age' => 0,
         'contexts' => array(),
       ),
+      'visibility' => $visibility,
     );
   }
 
@@ -112,11 +141,39 @@ public function calculateDependencies() {
    * {@inheritdoc}
    */
   public function access(AccountInterface $account) {
-    // @todo Move block visibility here in https://drupal.org/node/2278541.
+    // @todo Add in a context mapping until the UI supports configuring them,
+    //   see https://drupal.org/node/2284687.
+    $mappings['user_role']['current_user'] = 'user';
+
+    $conditions = $this->getVisibilityConditions();
+    $contexts = $this->getConditionContexts();
+    foreach ($conditions as $condition_id => $condition) {
+      if ($condition instanceof ContextAwarePluginInterface) {
+        if (!isset($mappings[$condition_id])) {
+          $mappings[$condition_id] = array();
+        }
+        $this->contextHandler()->applyContextMapping($condition, $contexts, $mappings[$condition_id]);
+      }
+    }
+    if ($this->resolveConditions($conditions, 'and', $contexts, $mappings) === 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() {
+    $conditions = $this->getVisibilityConditions();
+    $this->eventDispatcher()->dispatch(BlockEvents::CONDITION_CONTEXT, new BlockConditionContextEvent($conditions));
+    return $conditions->getConditionContexts();
+  }
+
+  /**
    * Indicates whether the block should be shown.
    *
    * @param \Drupal\Core\Session\AccountInterface $account
@@ -212,6 +269,53 @@ 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_tabs'] = array(
+      '#type' => 'vertical_tabs',
+      '#title' => $this->t('Visibility'),
+      '#parents' => array('visibility_tabs'),
+      '#attached' => array(
+        'library' => array(
+          'block/drupal.block',
+        ),
+      ),
+    );
+    foreach ($this->getVisibilityConditions() as $condition_id => $condition) {
+      $condition_form = $condition->buildConfigurationForm(array(), $form_state);
+      $condition_form['#type'] = 'details';
+      $condition_form['#title'] = $condition->getPluginDefinition()['label'];
+      $condition_form['#group'] = 'visibility_tabs';
+      $form['visibility'][$condition_id] = $condition_form;
+    }
+
+    // @todo Determine if there is a better way to rename the conditions.
+    if (isset($form['visibility']['node_type'])) {
+      $form['visibility']['node_type']['#title'] = $this->t('Content types');
+      $form['visibility']['node_type']['bundles']['#title'] = $this->t('Content types');
+      $form['visibility']['node_type']['negate']['#type'] = 'value';
+      $form['visibility']['node_type']['negate']['#title_display'] = 'invisible';
+      $form['visibility']['node_type']['negate']['#value'] = $form['visibility']['node_type']['negate']['#default_value'];
+    }
+    if (isset($form['visibility']['user_role'])) {
+      $form['visibility']['user_role']['#title'] = $this->t('Roles');
+      unset($form['visibility']['user_role']['roles']['#description']);
+      $form['visibility']['user_role']['negate']['#type'] = 'value';
+      $form['visibility']['user_role']['negate']['#value'] = $form['visibility']['user_role']['negate']['#default_value'];
+    }
+    if (isset($form['visibility']['request_path'])) {
+      $form['visibility']['request_path']['#title'] = $this->t('Pages');
+      $form['visibility']['request_path']['negate']['#type'] = 'radios';
+      $form['visibility']['request_path']['negate']['#title_display'] = 'invisible';
+      $form['visibility']['request_path']['negate']['#default_value'] = (int) $form['visibility']['request_path']['negate']['#default_value'];
+      $form['visibility']['request_path']['negate']['#options'] = array(
+        $this->t('Show for the listed pages'),
+        $this->t('Hide for the listed pages'),
+      );
+    }
+    if (isset($form['visibility']['language'])) {
+      $form['visibility']['language']['negate']['#type'] = 'value';
+      $form['visibility']['language']['negate']['#value'] = $form['visibility']['language']['negate']['#default_value'];
+    }
+
     // Add plugin-specific settings for this block type.
     $form += $this->blockForm($form, $form_state);
     return $form;
@@ -236,6 +340,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->getVisibilityConditions() 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);
   }
 
@@ -259,6 +372,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->getVisibilityConditions() 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);
     }
   }
@@ -347,4 +467,61 @@ public function isCacheable() {
     return $max_age === Cache::PERMANENT || $max_age > 0;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getVisibilityConditions() {
+    if (!isset($this->conditionBag)) {
+      $this->conditionBag = new ConditionPluginBag($this->conditionPluginManager(), $this->configuration['visibility']);
+    }
+    return $this->conditionBag;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getVisibilityCondition($instance_id) {
+    return $this->getVisibilityConditions()->get($instance_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setVisibilityConfig($instance_id, array $configuration) {
+    $this->getVisibilityConditions()->setInstanceConfiguration($instance_id, $configuration);
+    return $this;
+  }
+
+  /**
+   * 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;
+  }
+
+  /**
+   * Wraps the event dispatcher.
+   *
+   * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface
+   *   The event dispatcher.
+   */
+  protected function eventDispatcher() {
+    return \Drupal::service('event_dispatcher');
+  }
+
+  /**
+   * Wraps the context handler.
+   *
+   * @return \Drupal\Core\Plugin\Context\ContextHandlerInterface
+   */
+  protected function contextHandler() {
+    return \Drupal::service('context.handler');
+  }
+
 }
diff --git a/core/modules/block/src/BlockForm.php b/core/modules/block/src/BlockForm.php
index 9631404..af8641c 100644
--- a/core/modules/block/src/BlockForm.php
+++ b/core/modules/block/src/BlockForm.php
@@ -10,9 +10,6 @@
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Entity\EntityForm;
 use Drupal\Core\Entity\EntityManagerInterface;
-use Drupal\Core\Language\LanguageInterface;
-use Drupal\Core\Language\LanguageManagerInterface;
-use Drupal\language\ConfigurableLanguageManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -35,23 +32,13 @@ class BlockForm extends EntityForm {
   protected $storage;
 
   /**
-   * The language manager.
-   *
-   * @var \Drupal\Core\Language\LanguageManagerInterface
-   */
-  protected $languageManager;
-
-  /**
    * Constructs a BlockForm object.
    *
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
-   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
-   *   The language manager.
    */
-  public function __construct(EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
+  public function __construct(EntityManagerInterface $entity_manager) {
     $this->storage = $entity_manager->getStorage('block');
-    $this->languageManager = $language_manager;
   }
 
   /**
@@ -59,8 +46,7 @@ public function __construct(EntityManagerInterface $entity_manager, LanguageMana
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('entity.manager'),
-      $container->get('language_manager')
+      $container->get('entity.manager')
     );
   }
 
@@ -94,122 +80,6 @@ public function form(array $form, array &$form_state) {
       '#disabled' => !$entity->isNew(),
     );
 
-    // Visibility settings.
-    $form['visibility'] = array(
-      '#type' => 'vertical_tabs',
-      '#title' => $this->t('Visibility settings'),
-      '#attached' => array(
-        'library' => array(
-          'block/drupal.block',
-        ),
-      ),
-      '#tree' => TRUE,
-      '#weight' => 10,
-      '#parents' => array('visibility'),
-    );
-
-    // Per-path visibility.
-    $form['visibility']['path'] = array(
-      '#type' => 'details',
-      '#title' => $this->t('Pages'),
-      '#group' => 'visibility',
-      '#weight' => 0,
-    );
-
-    // @todo remove this access check and inject it in some other way. In fact
-    //   this entire visibility settings section probably needs a separate user
-    //   interface in the near future.
-    $visibility = $entity->get('visibility');
-    $access = $this->currentUser()->hasPermission('use PHP for settings');
-    if (!empty($visibility['path']['visibility']) && $visibility['path']['visibility'] == BLOCK_VISIBILITY_PHP && !$access) {
-      $form['visibility']['path']['visibility'] = array(
-        '#type' => 'value',
-        '#value' => BLOCK_VISIBILITY_PHP,
-      );
-      $form['visibility']['path']['pages'] = array(
-        '#type' => 'value',
-        '#value' => !empty($visibility['path']['pages']) ? $visibility['path']['pages'] : '',
-      );
-    }
-    else {
-      $options = array(
-        BLOCK_VISIBILITY_NOTLISTED => $this->t('All pages except those listed'),
-        BLOCK_VISIBILITY_LISTED => $this->t('Only the listed pages'),
-      );
-      $description = $this->t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %user for the current user's page and %user-wildcard for every user page. %front is the front page.", array('%user' => 'user', '%user-wildcard' => 'user/*', '%front' => '<front>'));
-
-      $form['visibility']['path']['visibility'] = array(
-        '#type' => 'radios',
-        '#title' => $this->t('Show block on specific pages'),
-        '#options' => $options,
-        '#default_value' => !empty($visibility['path']['visibility']) ? $visibility['path']['visibility'] : BLOCK_VISIBILITY_NOTLISTED,
-      );
-      $form['visibility']['path']['pages'] = array(
-        '#type' => 'textarea',
-        '#title' => '<span class="visually-hidden">' . $this->t('Pages') . '</span>',
-        '#default_value' => !empty($visibility['path']['pages']) ? $visibility['path']['pages'] : '',
-        '#description' => $description,
-      );
-    }
-
-    // Configure the block visibility per language.
-    if ($this->languageManager->isMultilingual() && $this->languageManager instanceof ConfigurableLanguageManagerInterface) {
-      $language_types = $this->languageManager->getLanguageTypes();
-
-      // Fetch languages.
-      $languages = $this->languageManager->getLanguages(LanguageInterface::STATE_ALL);
-      $langcodes_options = array();
-      foreach ($languages as $language) {
-        // @todo $language->name is not wrapped with t(), it should be replaced
-        //   by CMI translation implementation.
-        $langcodes_options[$language->id] = $language->name;
-      }
-      $form['visibility']['language'] = array(
-        '#type' => 'details',
-        '#title' => $this->t('Languages'),
-        '#group' => 'visibility',
-        '#weight' => 5,
-      );
-      // If there are multiple configurable language types, let the user pick
-      // which one should be applied to this visibility setting. This way users
-      // can limit blocks by interface language or content language for example.
-      $info = $this->languageManager->getDefinedLanguageTypesInfo();
-      $language_type_options = array();
-      foreach ($language_types as $type_key) {
-        $language_type_options[$type_key] = $info[$type_key]['name'];
-      }
-      $form['visibility']['language']['language_type'] = array(
-        '#type' => 'radios',
-        '#title' => $this->t('Language type'),
-        '#options' => $language_type_options,
-        '#default_value' => !empty($visibility['language']['language_type']) ? $visibility['language']['language_type'] : reset($language_types),
-        '#access' => count($language_type_options) > 1,
-      );
-      $form['visibility']['language']['langcodes'] = array(
-        '#type' => 'checkboxes',
-        '#title' => $this->t('Show this block only for specific languages'),
-        '#default_value' => !empty($visibility['language']['langcodes']) ? $visibility['language']['langcodes'] : array(),
-        '#options' => $langcodes_options,
-        '#description' => $this->t('Show this block only for the selected language(s). If you select no languages, the block will be visible in all languages.'),
-      );
-    }
-
-    // Per-role visibility.
-    $role_options = array_map(array('\Drupal\Component\Utility\String', 'checkPlain'), user_role_names());
-    $form['visibility']['role'] = array(
-      '#type' => 'details',
-      '#title' => $this->t('Roles'),
-      '#group' => 'visibility',
-      '#weight' => 10,
-    );
-    $form['visibility']['role']['roles'] = array(
-      '#type' => 'checkboxes',
-      '#title' => $this->t('Show block for specific roles'),
-      '#default_value' => !empty($visibility['role']['roles']) ? $visibility['role']['roles'] : array(),
-      '#options' => $role_options,
-      '#description' => $this->t('Show this block only for the selected role(s). If you select no roles, the block will be visible to all users.'),
-    );
-
     // Theme settings.
     if ($entity->get('theme')) {
       $form['theme'] = array(
@@ -276,8 +146,6 @@ protected function actions(array $form, array &$form_state) {
   public function validate(array $form, array &$form_state) {
     parent::validate($form, $form_state);
 
-    // Remove empty lines from the role visibility list.
-    $form_state['values']['visibility']['role']['roles'] = array_filter($form_state['values']['visibility']['role']['roles']);
     // The Block Entity form puts all block plugin form elements in the
     // settings form element, so just pass that to the block for validation.
     $settings = array(
@@ -324,21 +192,6 @@ public function submit(array $form, array &$form_state) {
   }
 
   /**
-   * {@inheritdoc}
-   */
-  public function buildEntity(array $form, array &$form_state) {
-    $entity = parent::buildEntity($form, $form_state);
-
-    // visibility__active_tab is Form API state and not configuration.
-    // @todo Fix vertical tabs.
-    $visibility = $entity->get('visibility');
-    unset($visibility['visibility__active_tab']);
-    $entity->set('visibility', $visibility);
-
-    return $entity;
-  }
-
-  /**
    * Generates a unique machine name for a block.
    *
    * @param \Drupal\block\BlockInterface $block
diff --git a/core/modules/block/src/BlockInterface.php b/core/modules/block/src/BlockInterface.php
index de0d9de..30179bd 100644
--- a/core/modules/block/src/BlockInterface.php
+++ b/core/modules/block/src/BlockInterface.php
@@ -32,4 +32,12 @@
    */
   public function getPlugin();
 
+  /**
+   * Returns an array of visibility condition configurations.
+   *
+   * @return array
+   *   An array of visibility condition configuration keyed by the condition ID.
+   */
+  public function getVisibility();
+
 }
diff --git a/core/modules/block/src/BlockPluginInterface.php b/core/modules/block/src/BlockPluginInterface.php
index f45ecc5..64fd12f 100644
--- a/core/modules/block/src/BlockPluginInterface.php
+++ b/core/modules/block/src/BlockPluginInterface.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\block;
 
+use Drupal\Component\Plugin\Context\ContextInterface;
 use Drupal\Core\Cache\CacheableInterface;
 use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Component\Plugin\ConfigurablePluginInterface;
@@ -140,4 +141,35 @@ public function blockSubmit($form, &$form_state);
    */
   public function getMachineNameSuggestion();
 
+  /**
+   * Gets conditions for this block.
+   *
+   * @return \Drupal\Core\Condition\ConditionInterface[]|\Drupal\Core\Condition\ConditionPluginBag
+   *   An array of configured condition plugins.
+   */
+  public function getVisibilityConditions();
+
+  /**
+   * Gets a visibility condition plugin instance.
+   *
+   * @param string $instance_id
+   *   The condition plugin instance ID.
+   *
+   * @return \Drupal\Core\Condition\ConditionInterface
+   *   A condition plugin.
+   */
+  public function getVisibilityCondition($instance_id);
+
+  /**
+   * Sets the visibility condition configuration.
+   *
+   * @param string $instance_id
+   *   The condition instance ID.
+   * @param array $configuration
+   *   The condition configuration.
+   *
+   * @return $this
+   */
+  public function setVisibilityConfig($instance_id, array $configuration);
+
 }
diff --git a/core/modules/block/src/Entity/Block.php b/core/modules/block/src/Entity/Block.php
index de6c971..547b9ff 100644
--- a/core/modules/block/src/Entity/Block.php
+++ b/core/modules/block/src/Entity/Block.php
@@ -87,13 +87,6 @@ class Block extends ConfigEntityBase implements BlockInterface, EntityWithPlugin
   protected $pluginBag;
 
   /**
-   * The visibility settings.
-   *
-   * @var array
-   */
-  protected $visibility;
-
-  /**
    * {@inheritdoc}
    */
   public function getPlugin() {
@@ -145,7 +138,6 @@ public function toArray() {
       'weight',
       'plugin',
       'settings',
-      'visibility',
     );
     foreach ($names as $name) {
       $properties[$name] = $this->get($name);
@@ -194,4 +186,11 @@ public function getListCacheTags() {
     return array('theme' => $this->theme);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getVisibility() {
+    return $this->getPlugin()->getVisibilityConditions()->getConfiguration();
+  }
+
 }
diff --git a/core/modules/block/src/Event/BlockConditionContextEvent.php b/core/modules/block/src/Event/BlockConditionContextEvent.php
new file mode 100644
index 0000000..a290ae6
--- /dev/null
+++ b/core/modules/block/src/Event/BlockConditionContextEvent.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\Event\BlockContextEvent.
+ */
+
+namespace Drupal\block\Event;
+
+use Drupal\Core\Condition\ConditionPluginBag;
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Wraps block conditions in order for event subscribers to add context.
+ *
+ * @see \Drupal\block\Event\BlockEvents::CONDITION_CONTEXT
+ */
+class BlockConditionContextEvent extends Event {
+
+  /**
+   * @var \Drupal\Core\Condition\ConditionPluginBag
+   */
+  protected $conditions;
+
+  /**
+   * @param \Drupal\Core\Condition\ConditionPluginBag $conditions
+   */
+  public function __construct(ConditionPluginBag $conditions) {
+    $this->conditions = $conditions;
+  }
+
+  /**
+   * @return \Drupal\block\BlockPluginInterface
+   */
+  public function getConditions() {
+    return $this->conditions;
+  }
+
+}
diff --git a/core/modules/block/src/Event/BlockEvents.php b/core/modules/block/src/Event/BlockEvents.php
new file mode 100644
index 0000000..070eb6c
--- /dev/null
+++ b/core/modules/block/src/Event/BlockEvents.php
@@ -0,0 +1,23 @@
+<?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.
+   *
+   * @see \Drupal\block\BlockBase::getConditionContexts()
+   * @see \Drupal\block\Event\BlockConditionContextEvent
+   */
+  const CONDITION_CONTEXT = 'block.condition_context';
+
+}
diff --git a/core/modules/block/src/EventSubscriber/BlockContextSubscriberBase.php b/core/modules/block/src/EventSubscriber/BlockContextSubscriberBase.php
new file mode 100644
index 0000000..683e83e
--- /dev/null
+++ b/core/modules/block/src/EventSubscriber/BlockContextSubscriberBase.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\EventSubscriber\BlockContextSubscriberBase.
+ */
+
+namespace Drupal\block\EventSubscriber;
+
+use Drupal\block\Event\BlockConditionContextEvent;
+use Drupal\block\Event\BlockEvents;
+use Drupal\Component\Plugin\Context\ContextInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * @todo.
+ */
+abstract class BlockContextSubscriberBase implements EventSubscriberInterface {
+
+  /**
+   * @var \Drupal\Core\Condition\ConditionPluginBag
+   */
+  protected $conditions;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[BlockEvents::CONDITION_CONTEXT][] = 'onBlockConditionContext';
+    return $events;
+  }
+
+  /**
+   * Subscribes to the event and delegates to the subclass.
+   */
+  public function onBlockConditionContext(BlockConditionContextEvent $event) {
+    $this->conditions = $event->getConditions();
+    $this->determineBlockContext();
+  }
+
+  /**
+   * Determines the contexts for a given block.
+   */
+  abstract protected function determineBlockContext();
+
+  /**
+   * Sets the condition context for a given name.
+   *
+   * @param string $name
+   *   The name of the context.
+   * @param \Drupal\Component\Plugin\Context\ContextInterface $context
+   *   The context to add.
+   *
+   * @return $this
+   */
+  public function addContext($name, ContextInterface $context) {
+    $this->conditions->addContext($name, $context);
+    return $this;
+  }
+
+}
diff --git a/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php b/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php
new file mode 100644
index 0000000..4fedd93
--- /dev/null
+++ b/core/modules/block/src/EventSubscriber/CurrentLanguageContext.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\EventSubscriber\CurrentLanguageContext.
+ */
+
+namespace Drupal\block\EventSubscriber;
+
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Plugin\Context\Context;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Sets the current language as a context.
+ */
+class CurrentLanguageContext extends BlockContextSubscriberBase {
+
+  use StringTranslationTrait;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * Constructs a new CurrentLanguageContext.
+   *
+   * @param \Drupal\Core\Language\LanguageManagerInterface
+   *   The language manager.
+   */
+  public function __construct(LanguageManagerInterface $language_manager) {
+    $this->languageManager = $language_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function determineBlockContext() {
+    $context = new Context(array(
+      'type' => 'language',
+      'label' => $this->t('Current language'),
+    ));
+    $context->setContextValue($this->languageManager->getCurrentLanguage());
+    $this->addContext('language', $context);
+  }
+
+}
diff --git a/core/modules/block/src/EventSubscriber/CurrentUserContext.php b/core/modules/block/src/EventSubscriber/CurrentUserContext.php
new file mode 100644
index 0000000..b8fde21
--- /dev/null
+++ b/core/modules/block/src/EventSubscriber/CurrentUserContext.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\Event\CurrentUserContext.
+ */
+
+namespace Drupal\block\EventSubscriber;
+
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Plugin\Context\Context;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Sets the current user as a context.
+ */
+class CurrentUserContext extends BlockContextSubscriberBase {
+
+  use StringTranslationTrait;
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $account;
+
+  /**
+   * The user storage.
+   *
+   * @var \Drupal\user\UserStorageInterface
+   */
+  protected $userStorage;
+
+  /**
+   * Constructs a new CurrentUserContext.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The current user.
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   */
+  public function __construct(AccountInterface $account, EntityManagerInterface $entity_manager) {
+    $this->account = $account;
+    $this->userStorage = $entity_manager->getStorage('user');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function determineBlockContext() {
+    $current_user = $this->userStorage->load($this->account->id());
+
+    $context = new Context(array(
+      'type' => 'entity:user',
+      'label' => $this->t('Current user'),
+    ));
+    $context->setContextValue($current_user);
+    $this->addContext('current_user', $context);
+  }
+
+}
diff --git a/core/modules/block/src/EventSubscriber/NodeRouteContext.php b/core/modules/block/src/EventSubscriber/NodeRouteContext.php
new file mode 100644
index 0000000..0fe726d
--- /dev/null
+++ b/core/modules/block/src/EventSubscriber/NodeRouteContext.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\EventSubscriber\NodeRouteContext.
+ */
+
+namespace Drupal\block\EventSubscriber;
+
+use Drupal\Core\Plugin\Context\Context;
+use Drupal\node\Entity\Node;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Sets the current node as a context on node routes.
+ */
+class NodeRouteContext extends BlockContextSubscriberBase {
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * Constructs a new NodeRouteContext.
+   *
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
+   */
+  public function __construct(RequestStack $request_stack) {
+    $this->requestStack = $request_stack;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function determineBlockContext() {
+    $request = $this->requestStack->getCurrentRequest();
+    if ($request->attributes->has(RouteObjectInterface::ROUTE_OBJECT) && ($route_contexts = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)->getOption('parameters')) && isset($route_contexts['node'])) {
+      $context = new Context($route_contexts['node']);
+      if ($request->attributes->has('node')) {
+        $context->setContextValue($request->attributes->get('node'));
+      }
+      $this->addContext('node', $context);
+    }
+    elseif ($request->attributes->get(RouteObjectInterface::ROUTE_NAME) == 'node.add') {
+      $node_type = $request->attributes->get('node_type');
+      $context = new Context(array('type' => 'entity:node'));
+      $context->setContextValue(Node::create(array('type' => $node_type->id())));
+      $this->addContext('node', $context);
+    }
+  }
+
+}
diff --git a/core/modules/block/src/Plugin/BlocksPluginBag.php b/core/modules/block/src/Plugin/BlocksPluginBag.php
new file mode 100644
index 0000000..c55af62
--- /dev/null
+++ b/core/modules/block/src/Plugin/BlocksPluginBag.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\Plugin\BlocksPluginBag.
+ */
+
+namespace Drupal\block\Plugin;
+
+use Drupal\block\BlockPluginInterface;
+use Drupal\Core\Plugin\DefaultPluginBag;
+
+/**
+ * Provides a collection of block plugins.
+ */
+class BlocksPluginBag extends DefaultPluginBag {
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return \Drupal\block\BlockPluginInterface
+   */
+  public function &get($instance_id) {
+    return parent::get($instance_id);
+  }
+
+  /**
+   * Returns all blocks keyed by their region.
+   *
+   * @return array
+   *   An associative array keyed by region, containing an associative array of
+   *   block plugins.
+   */
+  public function getAllByRegion() {
+    $region_assignments = array();
+    foreach ($this as $block_id => $block) {
+      $configuration = $block->getConfiguration();
+      $region = isset($configuration['region']) ? $configuration['region'] : NULL;
+      $region_assignments[$region][$block_id] = $block;
+    }
+    foreach ($region_assignments as $region => $region_assignment) {
+      // @todo Determine the reason this needs error suppression.
+      @uasort($region_assignment, function (BlockPluginInterface $a, BlockPluginInterface $b) {
+        $a_config = $a->getConfiguration();
+        $a_weight = isset($a_config['weight']) ? $a_config['weight'] : 0;
+        $b_config = $b->getConfiguration();
+        $b_weight = isset($b_config['weight']) ? $b_config['weight'] : 0;
+        if ($a_weight == $b_weight) {
+          return strcmp($a->label(), $b->label());
+        }
+        return $a_weight > $b_weight ? 1 : -1;
+      });
+      $region_assignments[$region] = $region_assignment;
+    }
+    return $region_assignments;
+  }
+
+}
diff --git a/core/modules/block/src/Plugin/PageVariant/FullPageVariant.php b/core/modules/block/src/Plugin/PageVariant/FullPageVariant.php
new file mode 100644
index 0000000..33810ad
--- /dev/null
+++ b/core/modules/block/src/Plugin/PageVariant/FullPageVariant.php
@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\Plugin\PageVariant\FullPageVariant.
+ */
+
+namespace Drupal\block\Plugin\PageVariant;
+
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\block\Plugin\BlocksPluginBag;
+use Drupal\Core\Display\VariantBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @PageVariant(
+ *   id = "full_page",
+ *   admin_label = @Translation("Full page")
+ * )
+ */
+class FullPageVariant extends VariantBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The block storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $blockStorage;
+
+  /**
+   * The current theme.
+   *
+   * @var string
+   */
+  protected $theme;
+
+  /**
+   * Constructs a new FullPageVariant.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin ID for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Entity\EntityStorageInterface $block_storage
+   *   The block entity storage.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $block_storage) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->blockStorage = $block_storage;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('entity.manager')->getStorage('block')
+    );
+  }
+
+  /**
+   * Sets the current theme for this page.
+   *
+   * @param string $theme
+   *   The current theme.
+   *
+   * @return $this
+   */
+  public function setTheme($theme) {
+    $this->theme = $theme;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRegionNames() {
+    return system_region_list($this->theme);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function render() {
+    $build = array();
+    // Load all region content assigned via blocks.
+    foreach ($this->getRegionAssignments() as $region => $blocks) {
+      /** @var $blocks \Drupal\block\BlockInterface[] */
+      foreach ($blocks as $key => $block) {
+        if ($block->access('view')) {
+          $build[$region][$key] = entity_view($block, 'block');
+        }
+      }
+      if (!empty($build[$region])) {
+        // block_list() already returned the blocks in sorted order.
+        $build[$region]['#sorted'] = TRUE;
+      }
+    }
+    return $build;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRegionAssignments() {
+    // Build an array of the region names in the right order.
+    $empty = array_fill_keys(array_keys($this->getRegionNames()), array());
+
+    // @todo Use block plugins directly instead of block entities.
+    //$full = $this->getBlockBag()->getAllByRegion();
+    $full = array();
+    foreach ($this->blockStorage->loadByProperties(array('theme' => $this->theme)) as $block_id => $block) {
+      $full[$block->get('region')][$block_id] = $block;
+    }
+
+    // Merge it with the actual values to maintain the region ordering.
+    $assignments = array_intersect_key(array_merge($empty, $full), $empty);
+    foreach ($assignments as &$assignment) {
+      uasort($assignment, 'Drupal\block\Entity\Block::sort');
+    }
+    return $assignments;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getBlockBag() {
+    if (!$this->blockPluginBag) {
+      $blocks = array();
+      foreach ($this->blockStorage->loadByProperties('block', array('theme' => $this->theme)) as $block_id => $block) {
+        /** @var $block \Drupal\block\BlockInterface */
+        $configuration = $block->get('settings');
+        $configuration['region'] = $block->get('region');
+        $configuration['uuid'] = $block_id;
+        $blocks[$block_id] = $configuration;
+      }
+      $this->blockPluginBag = new BlocksPluginBag(\Drupal::service('plugin.manager.block'), $blocks);
+    }
+    return $this->blockPluginBag;
+  }
+
+}
diff --git a/core/modules/block/src/Tests/BlockInterfaceTest.php b/core/modules/block/src/Tests/BlockInterfaceTest.php
index 0b71146..c69548e 100644
--- a/core/modules/block/src/Tests/BlockInterfaceTest.php
+++ b/core/modules/block/src/Tests/BlockInterfaceTest.php
@@ -44,6 +44,7 @@ public function testBlockInterface() {
       'label' => 'Custom Display Message',
     );
     $expected_configuration = array(
+      'visibility' => array(),
       'id' => 'test_block_instantiation',
       'label' => 'Custom Display Message',
       'provider' => 'block_test',
@@ -55,6 +56,7 @@ public function testBlockInterface() {
       'display_message' => 'no message set',
     );
     // Initial configuration of the block at construction time.
+    /** @var $display_block \Drupal\block\BlockPluginInterface */
     $display_block = $manager->createInstance('test_block_instantiation', $configuration);
     $this->assertIdentical($display_block->getConfiguration(), $expected_configuration, 'The block was configured correctly.');
 
@@ -124,7 +126,10 @@ public function testBlockInterface() {
     );
     $form_state = array();
     // Ensure there are no form elements that do not belong to the plugin.
-    $this->assertIdentical($display_block->buildConfigurationForm(array(), $form_state), $expected_form, 'Only the expected form elements were present.');
+    $actual_form = $display_block->buildConfigurationForm(array(), $form_state);
+    // Remove the visibility sections, as that just tests condition plugins.
+    unset($actual_form['visibility'], $actual_form['visibility_tabs']);
+    $this->assertIdentical($actual_form, $expected_form, 'Only the expected form elements were present.');
 
     $expected_build = array(
       '#children' => 'My custom display message.',
diff --git a/core/modules/block/src/Tests/BlockLanguageTest.php b/core/modules/block/src/Tests/BlockLanguageTest.php
index 246e92c..7984345 100644
--- a/core/modules/block/src/Tests/BlockLanguageTest.php
+++ b/core/modules/block/src/Tests/BlockLanguageTest.php
@@ -57,11 +57,11 @@ public function testLanguageBlockVisibility() {
     $default_theme = \Drupal::config('system.theme')->get('default');
     $this->drupalGet('admin/structure/block/add/system_powered_by_block' . '/' . $default_theme);
 
-    $this->assertField('visibility[language][langcodes][en]', 'Language visibility field is visible.');
+    $this->assertField('settings[visibility][language][langcodes][en]', 'Language visibility field is visible.');
 
     // Enable a standard block and set the visibility setting for one language.
     $edit = array(
-      'visibility[language][langcodes][en]' => TRUE,
+      'settings[visibility][language][langcodes][en]' => TRUE,
       'id' => strtolower($this->randomName(8)),
       'region' => 'sidebar_first',
     );
@@ -100,7 +100,7 @@ public function testLanguageBlockVisibilityLanguageDelete() {
     $block = $this->drupalPlaceBlock('system_powered_by_block', $edit);
 
     // Check that we have the language in config after saving the setting.
-    $visibility = $block->get('visibility');
+    $visibility = $block->getVisibility();
     $language = $visibility['language']['langcodes']['fr'];
     $this->assertTrue('fr' === $language, 'Language is set in the block configuration.');
 
@@ -110,7 +110,7 @@ public function testLanguageBlockVisibilityLanguageDelete() {
     // Check that the language is no longer stored in the configuration after
     // it is deleted.
     $block = entity_load('block', $block->id());
-    $visibility = $block->get('visibility');
+    $visibility = $block->getVisibility();
     $this->assertTrue(empty($visibility['language']['langcodes']['fr']), 'Language is no longer not set in the block configuration after deleting the block.');
   }
 
diff --git a/core/modules/block/src/Tests/BlockStorageUnitTest.php b/core/modules/block/src/Tests/BlockStorageUnitTest.php
index 65d67f6..6e4405e 100644
--- a/core/modules/block/src/Tests/BlockStorageUnitTest.php
+++ b/core/modules/block/src/Tests/BlockStorageUnitTest.php
@@ -99,6 +99,7 @@ protected function createTests() {
       'region' => '-1',
       'plugin' => 'test_html',
       'settings' => array(
+        'visibility' => array(),
         'id' => 'test_html',
         'label' => '',
         'provider' => 'block_test',
@@ -108,7 +109,6 @@ protected function createTests() {
           'contexts' => array(),
         ),
       ),
-      'visibility' => NULL,
     );
     $this->assertIdentical($actual_properties, $expected_properties);
 
diff --git a/core/modules/block/src/Tests/BlockTest.php b/core/modules/block/src/Tests/BlockTest.php
index 882863d..741fab1 100644
--- a/core/modules/block/src/Tests/BlockTest.php
+++ b/core/modules/block/src/Tests/BlockTest.php
@@ -40,8 +40,9 @@ function testBlockVisibility() {
     );
     // Set the block to be hidden on any user path, and to be shown only to
     // authenticated users.
-    $edit['visibility[path][pages]'] = 'user*';
-    $edit['visibility[role][roles][' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE;
+    $edit['settings[visibility][request_path][pages]'] = 'user*';
+    $edit['settings[visibility][request_path][negate]'] = TRUE;
+    $edit['settings[visibility][user_role][roles][' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE;
     $this->drupalPostForm('admin/structure/block/add/' . $block_name . '/' . $default_theme, $edit, t('Save block'));
     $this->assertText('The block configuration has been saved.', 'Block was saved');
 
@@ -65,8 +66,7 @@ function testBlockVisibility() {
   }
 
   /**
-   * Test block visibility when using "pages" restriction but leaving
-   * "pages" textarea empty
+   * Test block visibility when leaving "pages" textarea empty.
    */
   function testBlockVisibilityListedEmpty() {
     $block_name = 'system_powered_by_block';
@@ -78,7 +78,7 @@ function testBlockVisibilityListedEmpty() {
       'id' => strtolower($this->randomName(8)),
       'region' => 'sidebar_first',
       'settings[label]' => $title,
-      'visibility[path][visibility]' => BLOCK_VISIBILITY_LISTED,
+      'settings[visibility][request_path][negate]' => TRUE,
     );
     // Set the block to be hidden on any user path, and to be shown only to
     // authenticated users.
diff --git a/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml b/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml
index 232c64a..72510d0 100644
--- a/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml
+++ b/core/modules/block/tests/modules/block_test/config/install/block.block.test_block.yml
@@ -6,17 +6,10 @@ langcode: en
 region: '-1'
 plugin: test_html
 settings:
+  visibility: {  }
   label: 'Test HTML block'
   provider: block_test
   label_display: 'hidden'
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types: {  }
 dependencies:
   module:
     - block_test
diff --git a/core/modules/block/tests/src/BlockBaseTest.php b/core/modules/block/tests/src/BlockBaseTest.php
index 913130b..c036e15 100644
--- a/core/modules/block/tests/src/BlockBaseTest.php
+++ b/core/modules/block/tests/src/BlockBaseTest.php
@@ -9,7 +9,6 @@
 
 use Drupal\block_test\Plugin\Block\TestBlockInstantiation;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
-use Drupal\Core\Transliteration\PHPTransliteration;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -38,19 +37,78 @@ public function testGetMachineNameSuggestion() {
       ->setMethods(array('readLanguageOverrides'))
       ->getMock();
 
+    $condition_plugin_manager = $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface');
+    $condition_plugin_manager->expects($this->atLeastOnce())
+      ->method('getDefinitions')
+      ->will($this->returnValue(array()));
     $container = new ContainerBuilder();
+    $container->set('plugin.manager.condition', $condition_plugin_manager);
     $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() {
+    $plugin_manager = $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface');
+    $plugin_manager->expects($this->once())
+      ->method('getDefinitions')
+      ->will($this->returnValue(array(
+        'request_path' => array(
+          'id' => 'request_path',
+        ),
+        'user_role' => array(
+          'id' => 'user_role',
+        ),
+        'node_type' => array(
+          'id' => 'node_type',
+        ),
+        'language' => array(
+          'id' => 'language',
+        ),
+      )));
+    $container = new ContainerBuilder();
+    $container->set('plugin.manager.condition', $plugin_manager);
+    \Drupal::setContainer($container);
+    $config = array();
+    $definition = array(
+      'admin_label' => 'Admin label',
+      'provider' => 'block_test',
+    );
+
+    $block_base = new TestBlockInstantiation($config, 'test_block_instantiation', $definition);
+    $conditions_bag = $block_base->getVisibilityConditions();
+
+    $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->setVisibilityConfig($instance_id, $condition_config);
+
+    $plugin_manager->expects($this->once())->method('createInstance')
+      ->withAnyParameters()->will($this->returnValue('test'));
+
+    $condition = $block_base->getVisibilityCondition($instance_id);
+
+    $this->assertEquals('test', $condition, "The correct condition is returned.");
+  }
+
 }
diff --git a/core/modules/block/tests/src/Plugin/BlocksPluginBagTest.php b/core/modules/block/tests/src/Plugin/BlocksPluginBagTest.php
new file mode 100644
index 0000000..c4cd8ec
--- /dev/null
+++ b/core/modules/block/tests/src/Plugin/BlocksPluginBagTest.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\Tests\BlocksPluginBagTest.
+ */
+
+namespace Drupal\block\Tests;
+
+use Drupal\block\Plugin\BlocksPluginBag;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the block plugin bag.
+ *
+ * @coversDefaultClass \Drupal\block\Plugin\BlocksPluginBag
+ *
+ * @group Drupal
+ * @group Block
+ */
+class BlocksPluginBagTest extends UnitTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Blocks plugin bag',
+      'description' => '',
+      'group' => 'Block',
+    );
+  }
+
+  /**
+   * Tests the getAllByRegion() method.
+   *
+   * @covers ::getAllByRegion
+   */
+  public function testGetAllByRegion() {
+    $blocks = array(
+      'foo' => array(
+        'id' => 'foo',
+        'label' => 'Foo',
+        'plugin' => 'system_powered_by_block',
+        'region' => 'bottom',
+      ),
+      'bar' => array(
+        'id' => 'bar',
+        'label' => 'Bar',
+        'plugin' => 'system_powered_by_block',
+        'region' => 'top',
+      ),
+      'bing' => array(
+        'id' => 'bing',
+        'label' => 'Bing',
+        'plugin' => 'system_powered_by_block',
+        'region' => 'bottom',
+        'weight' => -10,
+      ),
+      'baz' => array(
+        'id' => 'baz',
+        'label' => 'Baz',
+        'plugin' => 'system_powered_by_block',
+        'region' => 'bottom',
+      ),
+    );
+    $plugins = array();
+    $plugin_map = array();
+    foreach ($blocks as $block_id => $block) {
+      $plugin = $this->getMock('Drupal\block\BlockPluginInterface');
+      $plugin->expects($this->any())
+        ->method('label')
+        ->will($this->returnValue($block['label']));
+      $plugin->expects($this->any())
+        ->method('getConfiguration')
+        ->will($this->returnValue($block));
+      $plugins[$block_id] = $plugin;
+      $plugin_map[] = array($block_id, $block, $plugin);
+    }
+    $block_manager = $this->getMock('Drupal\block\BlockManagerInterface');
+    $block_manager->expects($this->exactly(4))
+      ->method('createInstance')
+      ->will($this->returnValueMap($plugin_map));
+
+    $block_plugin_bag = new BlocksPluginBag($block_manager, $blocks);
+    $expected = array(
+      'bottom' => array(
+        'bing' => $plugins['bing'],
+        'baz' => $plugins['baz'],
+        'foo' => $plugins['foo'],
+      ),
+      'top' => array(
+        'bar' => $plugins['bar'],
+      ),
+    );
+    $this->assertSame($expected, $block_plugin_bag->getAllByRegion());
+  }
+
+}
diff --git a/core/modules/language/src/Plugin/Condition/Language.php b/core/modules/language/src/Plugin/Condition/Language.php
index e6268c2..41dab71 100644
--- a/core/modules/language/src/Plugin/Condition/Language.php
+++ b/core/modules/language/src/Plugin/Condition/Language.php
@@ -29,7 +29,6 @@ class Language extends ConditionPluginBase {
    * {@inheritdoc}
    */
   public function buildConfigurationForm(array $form, array &$form_state) {
-    $form = parent::buildConfigurationForm($form, $form_state);
     if (\Drupal::languageManager()->isMultilingual()) {
       // Fetch languages.
       $languages = language_list(LanguageInterface::STATE_ALL);
@@ -51,7 +50,7 @@ public function buildConfigurationForm(array $form, array &$form_state) {
         '#value' => $this->configuration['langcodes'],
       );
     }
-    return $form;
+    return parent::buildConfigurationForm($form, $form_state);
   }
 
   /**
diff --git a/core/modules/migrate_drupal/config/install/migrate.migration.d6_block.yml b/core/modules/migrate_drupal/config/install/migrate.migration.d6_block.yml
index 05bd07d..4fa9ea7 100644
--- a/core/modules/migrate_drupal/config/install/migrate.migration.d6_block.yml
+++ b/core/modules/migrate_drupal/config/install/migrate.migration.d6_block.yml
@@ -46,9 +46,6 @@ process:
   region: region
   theme: theme
   label: title
-  'visibility.path.visibility': visibility
-  'visibility.path.pages': pages
-  'visibility.role.roles': roles
   weight: weight
   settings:
     plugin: d6_block_settings
@@ -56,6 +53,9 @@ process:
       - @plugin
       - delta
       - settings
+      - visibility
+  'settings.visibility.request_path.pages': pages
+  'settings.visibility.user_role.roles': roles
 destination:
   plugin: entity:block
 migration_dependencies:
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/BlockSettings.php b/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/BlockSettings.php
index a804352..94ea600 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/BlockSettings.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/BlockSettings.php
@@ -24,8 +24,9 @@ class BlockSettings extends ProcessPluginBase {
    * Set the block configuration.
    */
   public function transform($value, MigrateExecutable $migrate_executable, Row $row, $destination_property) {
-    list($plugin, $delta, $old_settings) = $value;
+    list($plugin, $delta, $old_settings, $visibility) = $value;
     $settings = array();
+    $settings['visibility']['request_path']['negate'] = !$visibility;
     switch ($plugin) {
       case 'aggregator_feed_block':
         list(, $id) = explode('-', $delta);
diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateBlockTest.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateBlockTest.php
index 800e784..313e780 100644
--- a/core/modules/migrate_drupal/src/Tests/d6/MigrateBlockTest.php
+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateBlockTest.php
@@ -75,6 +75,7 @@ public function setUp() {
    * Test the block settings migration.
    */
   public function testBlockMigration() {
+    /** @var $blocks \Drupal\block\BlockInterface[] */
     $blocks = entity_load_multiple('block');
     $this->assertEqual(count($blocks), 11);
 
@@ -83,36 +84,36 @@ public function testBlockMigration() {
     $this->assertNotNull($test_block_user);
     $this->assertEqual('left', $test_block_user->get('region'));
     $this->assertEqual('garland', $test_block_user->get('theme'));
-    $visibility = $test_block_user->get('visibility');
-    $this->assertEqual(0, $visibility['path']['visibility']);
-    $this->assertEqual('', $visibility['path']['pages']);
+    $visibility = $test_block_user->getVisibility();
+    $this->assertEqual(TRUE, $visibility['request_path']['negate']);
+    $this->assertEqual('', $visibility['request_path']['pages']);
     $this->assertEqual(0, $test_block_user->weight);
 
     $test_block_user_1 = $blocks['user_1'];
     $this->assertNotNull($test_block_user_1);
     $this->assertEqual('left', $test_block_user_1->get('region'));
     $this->assertEqual('garland', $test_block_user_1->get('theme'));
-    $visibility = $test_block_user_1->get('visibility');
-    $this->assertEqual(0, $visibility['path']['visibility']);
-    $this->assertEqual('', $visibility['path']['pages']);
+    $visibility = $test_block_user_1->getVisibility();
+    $this->assertEqual(TRUE, $visibility['request_path']['negate']);
+    $this->assertEqual('', $visibility['request_path']['pages']);
     $this->assertEqual(0, $test_block_user_1->weight);
 
     $test_block_user_2 = $blocks['user_2'];
     $this->assertNotNull($test_block_user_2);
     $this->assertEqual('', $test_block_user_2->get('region'));
     $this->assertEqual('garland', $test_block_user_2->get('theme'));
-    $visibility = $test_block_user_2->get('visibility');
-    $this->assertEqual(0, $visibility['path']['visibility']);
-    $this->assertEqual('', $visibility['path']['pages']);
+    $visibility = $test_block_user_2->getVisibility();
+    $this->assertEqual(TRUE, $visibility['request_path']['negate']);
+    $this->assertEqual('', $visibility['request_path']['pages']);
     $this->assertEqual(-3, $test_block_user_2->weight);
 
     $test_block_user_3 = $blocks['user_3'];
     $this->assertNotNull($test_block_user_3);
     $this->assertEqual('', $test_block_user_3->get('region'));
     $this->assertEqual('garland', $test_block_user_3->get('theme'));
-    $visibility = $test_block_user_3->get('visibility');
-    $this->assertEqual(0, $visibility['path']['visibility']);
-    $this->assertEqual('', $visibility['path']['pages']);
+    $visibility = $test_block_user_3->getVisibility();
+    $this->assertEqual(TRUE, $visibility['request_path']['negate']);
+    $this->assertEqual('', $visibility['request_path']['pages']);
     $this->assertEqual(-1, $test_block_user_3->weight);
 
     // Check system block
@@ -120,9 +121,9 @@ public function testBlockMigration() {
     $this->assertNotNull($test_block_system);
     $this->assertEqual('footer', $test_block_system->get('region'));
     $this->assertEqual('garland', $test_block_system->get('theme'));
-    $visibility = $test_block_system->get('visibility');
-    $this->assertEqual(0, $visibility['path']['visibility']);
-    $this->assertEqual('', $visibility['path']['pages']);
+    $visibility = $test_block_system->getVisibility();
+    $this->assertEqual(TRUE, $visibility['request_path']['negate']);
+    $this->assertEqual('', $visibility['request_path']['pages']);
     $this->assertEqual(-5, $test_block_system->weight);
 
     // Check comment block
@@ -130,9 +131,9 @@ public function testBlockMigration() {
     $this->assertNotNull($test_block_comment);
     $this->assertEqual('', $test_block_comment->get('region'));
     $this->assertEqual('garland', $test_block_comment->get('theme'));
-    $visibility = $test_block_comment->get('visibility');
-    $this->assertEqual(0, $visibility['path']['visibility']);
-    $this->assertEqual('', $visibility['path']['pages']);
+    $visibility = $test_block_comment->getVisibility();
+    $this->assertEqual(TRUE, $visibility['request_path']['negate']);
+    $this->assertEqual('', $visibility['request_path']['pages']);
     $this->assertEqual(-6, $test_block_comment->weight);
 
     // Check menu blocks
@@ -140,18 +141,18 @@ public function testBlockMigration() {
     $this->assertNotNull($test_block_menu);
     $this->assertEqual('header', $test_block_menu->get('region'));
     $this->assertEqual('garland', $test_block_menu->get('theme'));
-    $visibility = $test_block_menu->get('visibility');
-    $this->assertEqual(0, $visibility['path']['visibility']);
-    $this->assertEqual('', $visibility['path']['pages']);
+    $visibility = $test_block_menu->getVisibility();
+    $this->assertEqual(TRUE, $visibility['request_path']['negate']);
+    $this->assertEqual('', $visibility['request_path']['pages']);
     $this->assertEqual(-5, $test_block_menu->weight);
 
     $test_block_menu_1 = $blocks['menu_1'];
     $this->assertNotNull($test_block_menu_1);
     $this->assertEqual('', $test_block_menu_1->get('region'));
     $this->assertEqual('garland', $test_block_menu_1->get('theme'));
-    $visibility = $test_block_menu_1->get('visibility');
-    $this->assertEqual(0, $visibility['path']['visibility']);
-    $this->assertEqual('', $visibility['path']['pages']);
+    $visibility = $test_block_menu_1->getVisibility();
+    $this->assertEqual(TRUE, $visibility['request_path']['negate']);
+    $this->assertEqual('', $visibility['request_path']['pages']);
     $this->assertEqual(-5, $test_block_menu_1->weight);
 
     // Check node block
@@ -159,9 +160,9 @@ public function testBlockMigration() {
     $this->assertNotNull($test_block_node);
     $this->assertEqual('', $test_block_node->get('region'));
     $this->assertEqual('garland', $test_block_node->get('theme'));
-    $visibility = $test_block_node->get('visibility');
-    $this->assertEqual(0, $visibility['path']['visibility']);
-    $this->assertEqual('', $visibility['path']['pages']);
+    $visibility = $test_block_node->getVisibility();
+    $this->assertEqual(TRUE, $visibility['request_path']['negate']);
+    $this->assertEqual('', $visibility['request_path']['pages']);
     $this->assertEqual(-4, $test_block_node->weight);
 
     // Check custom blocks
@@ -169,18 +170,18 @@ public function testBlockMigration() {
     $this->assertNotNull($test_block_block);
     $this->assertEqual('content', $test_block_block->get('region'));
     $this->assertEqual('garland', $test_block_block->get('theme'));
-    $visibility = $test_block_block->get('visibility');
-    $this->assertEqual(1, $visibility['path']['visibility']);
-    $this->assertEqual('<front>', $visibility['path']['pages']);
+    $visibility = $test_block_block->getVisibility();
+    $this->assertEqual(FALSE, $visibility['request_path']['negate']);
+    $this->assertEqual('<front>', $visibility['request_path']['pages']);
     $this->assertEqual(0, $test_block_block->weight);
 
     $test_block_block_1 = $blocks['block_1'];
     $this->assertNotNull($test_block_block_1);
     $this->assertEqual('right', $test_block_block_1->get('region'));
     $this->assertEqual('bluemarine', $test_block_block_1->get('theme'));
-    $visibility = $test_block_block_1->get('visibility');
-    $this->assertEqual(1, $visibility['path']['visibility']);
-    $this->assertEqual('node', $visibility['path']['pages']);
+    $visibility = $test_block_block_1->getVisibility();
+    $this->assertEqual(FALSE, $visibility['request_path']['negate']);
+    $this->assertEqual('node', $visibility['request_path']['pages']);
     $this->assertEqual(-4, $test_block_block_1->weight);
   }
 }
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index d4f7db0..f094d61 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -933,71 +933,6 @@ function node_get_recent($number = 10) {
 }
 
 /**
- * Implements hook_form_FORM_ID_alter() for block_form().
- *
- * Adds node-type specific visibility options to block configuration form.
- */
-function node_form_block_form_alter(&$form, &$form_state) {
-  $block = $form_state['controller']->getEntity();
-  $visibility = $block->get('visibility');
-  $form['visibility']['node_type'] = array(
-    '#type' => 'details',
-    '#title' => t('Content types'),
-    '#group' => 'visibility',
-    '#weight' => 5,
-  );
-  $form['visibility']['node_type']['types'] = array(
-    '#type' => 'checkboxes',
-    '#title' => t('Show block for specific content types'),
-    '#default_value' => !empty($visibility['node_type']['types']) ? $visibility['node_type']['types'] : array(),
-    '#options' => node_type_get_names(),
-    '#description' => t('Show this block only on pages that display content of the given type(s). If you select no types, there will be no type-specific limitation.'),
-  );
-}
-
-/**
- * Implements hook_block_access().
- *
- * Checks the content type specific visibility settings and removes the block
- * if the visibility conditions are not met.
- */
-function node_block_access(Block $block, $operation, AccountInterface $account, $langcode) {
-  // Only affect access when viewing the block.
-  if ($operation != 'view') {
-    return;
-  }
-  $visibility = $block->get('visibility');
-  if (!empty($visibility)) {
-    if (!empty($visibility['node_type']['types'])) {
-      $allowed_types = array_filter($visibility['node_type']['types']);
-    }
-    if (empty($allowed_types)) {
-      // There are no node types selected in visibility settings so there is
-      // nothing to do.
-      // @see node_form_block_form_alter()
-      return;
-    }
-
-    // For blocks with node types associated, if the node type does not match
-    // the settings from this block, deny access to it.
-    $request = \Drupal::request();
-    if ($node = $request->attributes->get('node')) {
-      // Page has node.
-      return in_array($node->bundle(), $allowed_types);
-    }
-    // This is a node creation page.
-    // $request->attributes->has('node_type') would also match administration
-    // configuration pages, which the previous URI path options did not.
-    if ($request->attributes->get(RouteObjectInterface::ROUTE_NAME) == 'node.add') {
-      $node_type = $request->attributes->get('node_type');
-      return in_array($node_type->id(), $allowed_types);
-    }
-
-    return FALSE;
-  }
-}
-
-/**
  * Page callback: Generates and prints an RSS feed.
  *
  * Generates an RSS feed from an array of node IDs, and prints it with an HTTP
diff --git a/core/modules/node/src/Plugin/Condition/NodeType.php b/core/modules/node/src/Plugin/Condition/NodeType.php
index 5b093bf..66e57e3 100644
--- a/core/modules/node/src/Plugin/Condition/NodeType.php
+++ b/core/modules/node/src/Plugin/Condition/NodeType.php
@@ -28,7 +28,6 @@ class NodeType extends ConditionPluginBase {
    * {@inheritdoc}
    */
   public function buildConfigurationForm(array $form, array &$form_state) {
-    $form = parent::buildConfigurationForm($form, $form_state);
     $options = array();
     foreach (node_type_get_types() as $type) {
       $options[$type->type] = $type->name;
@@ -39,7 +38,7 @@ public function buildConfigurationForm(array $form, array &$form_state) {
       '#options' => $options,
       '#default_value' => $this->configuration['bundles'],
     );
-    return $form;
+    return parent::buildConfigurationForm($form, $form_state);
   }
 
   /**
diff --git a/core/modules/node/src/Tests/NodeBlockFunctionalTest.php b/core/modules/node/src/Tests/NodeBlockFunctionalTest.php
index 148ec23..e806bc9 100644
--- a/core/modules/node/src/Tests/NodeBlockFunctionalTest.php
+++ b/core/modules/node/src/Tests/NodeBlockFunctionalTest.php
@@ -128,14 +128,14 @@ public function testRecentNodeBlock() {
     $block = $this->drupalPlaceBlock('system_powered_by_block', array(
       'visibility' => array(
         'node_type' => array(
-          'types' => array(
+          'bundles' => array(
             'article' => 'article',
           ),
         ),
       ),
     ));
-    $visibility = $block->get('visibility');
-    $this->assertTrue(isset($visibility['node_type']['types']['article']), 'Visibility settings were saved to configuration');
+    $visibility = $block->getVisibility();
+    $this->assertTrue(isset($visibility['node_type']['bundles']['article']), 'Visibility settings were saved to configuration');
 
     // Create a page node.
     $node5 = $this->drupalCreateNode(array('uid' => $this->adminUser->id(), 'type' => 'page'));
diff --git a/core/modules/search/src/Tests/SearchBlockTest.php b/core/modules/search/src/Tests/SearchBlockTest.php
index d4e45db..311f69a 100644
--- a/core/modules/search/src/Tests/SearchBlockTest.php
+++ b/core/modules/search/src/Tests/SearchBlockTest.php
@@ -63,9 +63,9 @@ protected function testSearchFormBlock() {
     $this->assertResponse(200);
     $this->assertText('Your search yielded no results');
 
-    $visibility = $block->get('visibility');
-    $visibility['path']['pages'] = 'search';
-    $block->set('visibility', $visibility);
+    $visibility = $block->getVisibility();
+    $visibility['request_path']['pages'] = 'search';
+    $block->getPlugin()->setVisibilityConfig('request_path', $visibility['request_path']);
 
     $this->submitGetForm('', $terms, t('Search'));
     $this->assertResponse(200);
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index 6aa9767..06ddcb5 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -427,11 +427,14 @@ protected function drupalPlaceBlock($plugin_id, array $settings = array()) {
         'max_age' => 0,
       ),
     );
-    foreach (array('region', 'id', 'theme', 'plugin', 'visibility', 'weight') as $key) {
+    foreach (array('region', 'id', 'theme', 'plugin', 'weight') as $key) {
       $values[$key] = $settings[$key];
       // Remove extra values that do not belong in the settings array.
       unset($settings[$key]);
     }
+    foreach ($settings['visibility'] as $id => $visibility) {
+      $settings['visibility'][$id]['id'] = $id;
+    }
     $values['settings'] = $settings;
     $block = entity_create('block', $values);
     $block->save();
diff --git a/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php b/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
index f310b24..a22dfb2 100644
--- a/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
+++ b/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
@@ -67,8 +67,7 @@ function testPageCacheTags() {
     // Place a block, but only make it visible on full node page 2.
     $block = $this->drupalPlaceBlock('views_block:comments_recent-block_1', array(
       'visibility' => array(
-        'path' => array(
-          'visibility' => BLOCK_VISIBILITY_LISTED,
+        'request_path' => array(
           'pages' => 'node/' . $node_2->id(),
         ),
       )
diff --git a/core/modules/user/src/Plugin/Condition/UserRole.php b/core/modules/user/src/Plugin/Condition/UserRole.php
index e9740da..cd38ee3 100644
--- a/core/modules/user/src/Plugin/Condition/UserRole.php
+++ b/core/modules/user/src/Plugin/Condition/UserRole.php
@@ -28,7 +28,6 @@ class UserRole extends ConditionPluginBase {
    * {@inheritdoc}
    */
   public function buildConfigurationForm(array $form, array &$form_state) {
-    $form = parent::buildConfigurationForm($form, $form_state);
     $form['roles'] = array(
       '#type' => 'checkboxes',
       '#title' => $this->t('When the user has the following roles'),
@@ -36,7 +35,7 @@ public function buildConfigurationForm(array $form, array &$form_state) {
       '#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.'),
     );
-    return $form;
+    return parent::buildConfigurationForm($form, $form_state);
   }
 
   /**
diff --git a/core/modules/views/src/Tests/Plugin/BlockDependenciesTest.php b/core/modules/views/src/Tests/Plugin/BlockDependenciesTest.php
index dd2d2e8..0475e23 100644
--- a/core/modules/views/src/Tests/Plugin/BlockDependenciesTest.php
+++ b/core/modules/views/src/Tests/Plugin/BlockDependenciesTest.php
@@ -109,11 +109,14 @@ protected function createBlock($plugin_id, array $settings = array()) {
         'max_age' => 0,
       ),
     );
-    foreach (array('region', 'id', 'theme', 'plugin', 'visibility', 'weight') as $key) {
+    foreach (array('region', 'id', 'theme', 'plugin', 'weight') as $key) {
       $values[$key] = $settings[$key];
       // Remove extra values that do not belong in the settings array.
       unset($settings[$key]);
     }
+    foreach ($settings['visibility'] as $id => $visibility) {
+      $settings['visibility'][$id]['id'] = $id;
+    }
     $values['settings'] = $settings;
     $block = entity_create('block', $values);
     $block->save();
diff --git a/core/modules/views/tests/src/Plugin/Block/ViewsBlockTest.php b/core/modules/views/tests/src/Plugin/Block/ViewsBlockTest.php
index eb46cb3..c1029dd 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,13 @@ public static function getInfo() {
    */
   protected function setUp() {
     parent::setUp(); // TODO: Change the autogenerated stub
+    $condition_plugin_manager = $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface');
+    $condition_plugin_manager->expects($this->any())
+      ->method('getDefinitions')
+      ->will($this->returnValue(array()));
+    $container = new ContainerBuilder();
+    $container->set('plugin.manager.condition', $condition_plugin_manager);
+    \Drupal::setContainer($container);
 
     $this->executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
       ->disableOriginalConstructor()
@@ -128,6 +136,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 +162,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/modules/views_ui/src/Tests/OverrideDisplaysTest.php b/core/modules/views_ui/src/Tests/OverrideDisplaysTest.php
index 3384b0f..1a405d9 100644
--- a/core/modules/views_ui/src/Tests/OverrideDisplaysTest.php
+++ b/core/modules/views_ui/src/Tests/OverrideDisplaysTest.php
@@ -120,9 +120,9 @@ function testWizardMixedDefaultOverriddenDisplays() {
     // presence/absence of the view's title in both the page and the block).
     $this->drupalPlaceBlock("views_block:{$view['id']}-block_1", array(
       'visibility' => array(
-        'path' => array(
-          'visibility' => BLOCK_VISIBILITY_NOTLISTED,
+        'request_path' => array(
           'pages' => $view['page[path]'],
+          'negate' => TRUE,
         ),
       ),
     ));
diff --git a/core/profiles/minimal/config/install/block.block.stark_admin.yml b/core/profiles/minimal/config/install/block.block.stark_admin.yml
index 5d5c2c1..a7cc0a5 100644
--- a/core/profiles/minimal/config/install/block.block.stark_admin.yml
+++ b/core/profiles/minimal/config/install/block.block.stark_admin.yml
@@ -6,17 +6,10 @@ langcode: en
 region: sidebar_first
 plugin: 'system_menu_block:admin'
 settings:
+  visibility: {  }
   label: Administration
   provider: system
   label_display: visible
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types: {  }
 dependencies:
   entity:
     - system.menu.admin
diff --git a/core/profiles/minimal/config/install/block.block.stark_login.yml b/core/profiles/minimal/config/install/block.block.stark_login.yml
index 91ba3e0..9081e1a 100644
--- a/core/profiles/minimal/config/install/block.block.stark_login.yml
+++ b/core/profiles/minimal/config/install/block.block.stark_login.yml
@@ -6,17 +6,10 @@ langcode: en
 region: sidebar_first
 plugin: user_login_block
 settings:
+  visibility: {  }
   label: 'User login'
   provider: user
   label_display: visible
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types: {  }
 dependencies:
   module:
     - user
diff --git a/core/profiles/minimal/config/install/block.block.stark_tools.yml b/core/profiles/minimal/config/install/block.block.stark_tools.yml
index 36481b3..aeb99bb 100644
--- a/core/profiles/minimal/config/install/block.block.stark_tools.yml
+++ b/core/profiles/minimal/config/install/block.block.stark_tools.yml
@@ -6,17 +6,10 @@ langcode: en
 region: sidebar_first
 plugin: 'system_menu_block:tools'
 settings:
+  visibility: {  }
   label: Tools
   provider: system
   label_display: visible
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types: {  }
 dependencies:
   entity:
     - system.menu.tools
diff --git a/core/profiles/standard/config/install/block.block.bartik_breadcrumbs.yml b/core/profiles/standard/config/install/block.block.bartik_breadcrumbs.yml
index 23cc5d0..2128a61 100644
--- a/core/profiles/standard/config/install/block.block.bartik_breadcrumbs.yml
+++ b/core/profiles/standard/config/install/block.block.bartik_breadcrumbs.yml
@@ -6,19 +6,11 @@ langcode: en
 region: '-1'
 plugin: system_breadcrumb_block
 settings:
+  visibility: {  }
+  id: system_breadcrumb_block
   label: Breadcrumbs
   provider: system
   label_display: '0'
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types:
-      article: '0'
-      page: '0'
 dependencies:
   module:
     - system
diff --git a/core/profiles/standard/config/install/block.block.bartik_content.yml b/core/profiles/standard/config/install/block.block.bartik_content.yml
index dd89d12..d30a7f3 100644
--- a/core/profiles/standard/config/install/block.block.bartik_content.yml
+++ b/core/profiles/standard/config/install/block.block.bartik_content.yml
@@ -6,19 +6,11 @@ langcode: en
 region: content
 plugin: system_main_block
 settings:
+  visibility: {  }
+  id: system_main_block
   label: 'Main page content'
   provider: system
   label_display: '0'
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types:
-      article: '0'
-      page: '0'
 dependencies:
   module:
     - system
diff --git a/core/profiles/standard/config/install/block.block.bartik_footer.yml b/core/profiles/standard/config/install/block.block.bartik_footer.yml
index ef96ea9..26acc46 100644
--- a/core/profiles/standard/config/install/block.block.bartik_footer.yml
+++ b/core/profiles/standard/config/install/block.block.bartik_footer.yml
@@ -6,19 +6,11 @@ langcode: en
 region: footer
 plugin: 'system_menu_block:footer'
 settings:
+  visibility: {  }
+  id: 'system_menu_block:footer'
   label: 'Footer menu'
   provider: system
   label_display: visible
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types:
-      article: '0'
-      page: '0'
 dependencies:
   entity:
     - system.menu.footer
diff --git a/core/profiles/standard/config/install/block.block.bartik_help.yml b/core/profiles/standard/config/install/block.block.bartik_help.yml
index 3e139c6..3a2f48a 100644
--- a/core/profiles/standard/config/install/block.block.bartik_help.yml
+++ b/core/profiles/standard/config/install/block.block.bartik_help.yml
@@ -6,19 +6,11 @@ langcode: en
 region: help
 plugin: system_help_block
 settings:
+  visibility: {  }
+  id: system_help_block
   label: 'System Help'
   provider: system
   label_display: '0'
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types:
-      article: '0'
-      page: '0'
 dependencies:
   module:
     - system
diff --git a/core/profiles/standard/config/install/block.block.bartik_login.yml b/core/profiles/standard/config/install/block.block.bartik_login.yml
index 1465ce8..6d58c23 100644
--- a/core/profiles/standard/config/install/block.block.bartik_login.yml
+++ b/core/profiles/standard/config/install/block.block.bartik_login.yml
@@ -6,19 +6,11 @@ langcode: en
 region: sidebar_first
 plugin: user_login_block
 settings:
+  visibility: {  }
+  id: user_login_block
   label: 'User login'
   provider: user
   label_display: visible
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types:
-      article: '0'
-      page: '0'
 dependencies:
   module:
     - user
diff --git a/core/profiles/standard/config/install/block.block.bartik_powered.yml b/core/profiles/standard/config/install/block.block.bartik_powered.yml
index c722231..106c279 100644
--- a/core/profiles/standard/config/install/block.block.bartik_powered.yml
+++ b/core/profiles/standard/config/install/block.block.bartik_powered.yml
@@ -6,19 +6,11 @@ langcode: en
 region: footer
 plugin: system_powered_by_block
 settings:
+  visibility: {  }
+  id: system_powered_by_block
   label: 'Powered by Drupal'
   provider: system
   label_display: '0'
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types:
-      article: '0'
-      page: '0'
 dependencies:
   module:
     - system
diff --git a/core/profiles/standard/config/install/block.block.bartik_search.yml b/core/profiles/standard/config/install/block.block.bartik_search.yml
index 7b00a70..ac4130a 100644
--- a/core/profiles/standard/config/install/block.block.bartik_search.yml
+++ b/core/profiles/standard/config/install/block.block.bartik_search.yml
@@ -6,19 +6,11 @@ langcode: en
 region: sidebar_first
 plugin: search_form_block
 settings:
+  visibility: {  }
+  id: search_form_block
   label: Search
   provider: search
   label_display: visible
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types:
-      article: '0'
-      page: '0'
 dependencies:
   module:
     - search
diff --git a/core/profiles/standard/config/install/block.block.bartik_tools.yml b/core/profiles/standard/config/install/block.block.bartik_tools.yml
index 12efff1..6ed6808 100644
--- a/core/profiles/standard/config/install/block.block.bartik_tools.yml
+++ b/core/profiles/standard/config/install/block.block.bartik_tools.yml
@@ -6,19 +6,11 @@ langcode: en
 region: sidebar_first
 plugin: 'system_menu_block:tools'
 settings:
+  visibility: {  }
+  id: 'system_menu_block:tools'
   label: Tools
   provider: system
   label_display: visible
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types:
-      article: '0'
-      page: '0'
 dependencies:
   entity:
     - system.menu.tools
diff --git a/core/profiles/standard/config/install/block.block.seven_breadcrumbs.yml b/core/profiles/standard/config/install/block.block.seven_breadcrumbs.yml
index 844a546..cd7d7ac 100644
--- a/core/profiles/standard/config/install/block.block.seven_breadcrumbs.yml
+++ b/core/profiles/standard/config/install/block.block.seven_breadcrumbs.yml
@@ -6,19 +6,11 @@ langcode: en
 region: '-1'
 plugin: system_breadcrumb_block
 settings:
+  visibility: {  }
+  id: system_breadcrumb_block
   label: Breadcrumbs
   provider: system
   label_display: '0'
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types:
-      article: '0'
-      page: '0'
 dependencies:
   module:
     - system
diff --git a/core/profiles/standard/config/install/block.block.seven_content.yml b/core/profiles/standard/config/install/block.block.seven_content.yml
index ec7a432..12c2c5f 100644
--- a/core/profiles/standard/config/install/block.block.seven_content.yml
+++ b/core/profiles/standard/config/install/block.block.seven_content.yml
@@ -6,19 +6,11 @@ langcode: en
 region: content
 plugin: system_main_block
 settings:
+  visibility: {  }
+  id: system_main_block
   label: 'Main page content'
   provider: system
   label_display: '0'
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types:
-      article: '0'
-      page: '0'
 dependencies:
   module:
     - system
diff --git a/core/profiles/standard/config/install/block.block.seven_help.yml b/core/profiles/standard/config/install/block.block.seven_help.yml
index f54a908..1cf9fa3 100644
--- a/core/profiles/standard/config/install/block.block.seven_help.yml
+++ b/core/profiles/standard/config/install/block.block.seven_help.yml
@@ -6,19 +6,11 @@ langcode: en
 region: help
 plugin: system_help_block
 settings:
+  visibility: {  }
+  id: system_help_block
   label: 'System Help'
   provider: system
   label_display: '0'
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types:
-      article: '0'
-      page: '0'
 dependencies:
   module:
     - system
diff --git a/core/profiles/standard/config/install/block.block.seven_login.yml b/core/profiles/standard/config/install/block.block.seven_login.yml
index 9f4578c..321d92b 100644
--- a/core/profiles/standard/config/install/block.block.seven_login.yml
+++ b/core/profiles/standard/config/install/block.block.seven_login.yml
@@ -6,19 +6,11 @@ langcode: en
 region: content
 plugin: user_login_block
 settings:
+  visibility: {  }
+  id: user_login_block
   label: 'User login'
   provider: user
   label_display: visible
-visibility:
-  path:
-    visibility: 0
-    pages: ''
-  role:
-    roles: {  }
-  node_type:
-    types:
-      article: '0'
-      page: '0'
 dependencies:
   module:
     - user
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/Display/PageVariantTest.php b/core/tests/Drupal/Tests/Core/Display/PageVariantTest.php
new file mode 100644
index 0000000..f0071e8
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Display/PageVariantTest.php
@@ -0,0 +1,212 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Display\PageVariantTest.
+ */
+
+namespace Drupal\Tests\Core\Display;
+
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the base variant plugin.
+ *
+ * @coversDefaultClass \Drupal\Core\Display\VariantBase
+ *
+ * @group Drupal
+ * @group DisplayVariant
+ */
+class PageVariantTest extends UnitTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Display variant',
+      'description' => '',
+      'group' => 'Display variant',
+    );
+  }
+
+  /**
+   * Sets up a page variant plugin for testing.
+   *
+   * @param array $configuration
+   *   An array of plugin configuration.
+   * @param array $definition
+   *   The plugin definition array.
+   *
+   * @return \Drupal\Core\Display\VariantBase|\PHPUnit_Framework_MockObject_MockObject
+   *   A mocked page variant plugin.
+   */
+  public function setUpPageVariant($configuration = array(), $definition = array()) {
+    return $this->getMockBuilder('Drupal\Core\Display\VariantBase')
+      ->setConstructorArgs(array($configuration, 'test', $definition))
+      ->setMethods(array('getRegionNames', 'access', 'render', 'getBlockBag'))
+      ->getMock();
+  }
+
+  /**
+   * Tests the label() method.
+   *
+   * @covers ::label
+   */
+  public function testLabel() {
+    $page_variant = $this->setUpPageVariant(array('label' => 'foo'));
+    $this->assertSame('foo', $page_variant->label());
+  }
+
+  /**
+   * Tests the label() method using a default value.
+   *
+   * @covers ::label
+   */
+  public function testLabelDefault() {
+    $page_variant = $this->setUpPageVariant();
+    $this->assertSame('', $page_variant->label());
+  }
+
+  /**
+   * Tests the getWeight() method.
+   *
+   * @covers ::getWeight
+   */
+  public function testGetWeight() {
+    $page_variant = $this->setUpPageVariant(array('weight' => 5));
+    $this->assertSame(5, $page_variant->getWeight());
+  }
+
+  /**
+   * Tests the getWeight() method using a default value.
+   *
+   * @covers ::getWeight
+   */
+  public function testGetWeightDefault() {
+    $page_variant = $this->setUpPageVariant();
+    $this->assertSame(0, $page_variant->getWeight());
+  }
+
+  /**
+   * Tests the getRegionName() method.
+   *
+   * @covers ::getRegionName
+   *
+   * @dataProvider providerTestGetRegionName
+   */
+  public function testGetRegionName($region_name, $expected) {
+    $page_variant = $this->setUpPageVariant();
+    $page_variant->expects($this->once())
+      ->method('getRegionNames')
+      ->will($this->returnValue(array(
+        'test1' => 'Test 1',
+        'test2' => 'Test 2',
+      )));
+    $this->assertSame($expected, $page_variant->getRegionName($region_name));
+  }
+
+  public function providerTestGetRegionName() {
+    return array(
+      array('test1', 'Test 1'),
+      array('test2', 'Test 2'),
+      array('test3', ''),
+    );
+  }
+
+  /**
+   * Tests the getRegionAssignments() method.
+   *
+   * @covers ::getRegionAssignments
+   *
+   * @dataProvider providerTestGetRegionAssignments
+   */
+  public function testGetRegionAssignments($expected, $blocks = array()) {
+    $block_bag = $this->getMockBuilder('Drupal\block\Plugin\BlocksPluginBag')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $block_bag->expects($this->once())
+      ->method('getAllByRegion')
+      ->will($this->returnValue($blocks));
+
+    $page_variant = $this->setUpPageVariant();
+    $page_variant->expects($this->once())
+      ->method('getBlockBag')
+      ->will($this->returnValue($block_bag));
+    $page_variant->expects($this->once())
+      ->method('getRegionNames')
+      ->will($this->returnValue(array(
+        'test1' => 'Test 1',
+        'test2' => 'Test 2',
+      )));
+
+    $this->assertSame($expected, $page_variant->getRegionAssignments());
+  }
+
+  public function providerTestGetRegionAssignments() {
+    return array(
+      array(
+        array(
+          'test1' => array(),
+          'test2' => array(),
+        ),
+      ),
+      array(
+        array(
+          'test1' => array('foo'),
+          'test2' => array(),
+        ),
+        array(
+          'test1' => array('foo'),
+        ),
+      ),
+      array(
+        array(
+          'test1' => array(),
+          'test2' => array(),
+        ),
+        array(
+          'test3' => array('foo'),
+        ),
+      ),
+      array(
+        array(
+          'test1' => array(),
+          'test2' => array('foo'),
+        ),
+        array(
+          'test2' => array('foo'),
+          'test3' => array('bar'),
+        ),
+      ),
+    );
+  }
+
+  /**
+   * Tests the getConfiguration() method.
+   *
+   * @covers ::getConfiguration
+   */
+  public function testGetConfiguration() {
+    $block_bag = $this->getMockBuilder('Drupal\block\Plugin\BlocksPluginBag')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $block_bag->expects($this->once())
+      ->method('getConfiguration')
+      ->will($this->returnValue(array()));
+    $page_variant = $this->setUpPageVariant();
+    $page_variant->expects($this->once())
+      ->method('getBlockBag')
+      ->will($this->returnValue($block_bag));
+
+    $expected = array(
+      'id' => 'test',
+      'blocks' => array(),
+      'label' => '',
+      'uuid' => '',
+      'weight' => 0,
+    );
+    $this->assertSame($expected, $page_variant->getConfiguration());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php
index d162042..fa7c80c 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php
@@ -16,7 +16,7 @@
 /**
  * Tests the ContextHandler class.
  *
- * @coversDefaultClass \Drupal\Core\Plugin\ContextHandler
+ * @coversDefaultClass \Drupal\Core\Plugin\Context\ContextHandler
  *
  * @group Drupal
  * @group Plugin
