diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php
index 39e99af..e904a96 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
 
+use Drupal\Core\Plugin\Plugin\DataType\DataTypeWrapper;
 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\ConstraintValidator;
 
@@ -18,6 +19,17 @@ public function validate($entity, Constraint $constraint) {
       return;
     }
 
+    // Validate against wrapper bundle constraint when necessary.
+    if ($entity instanceof DataTypeWrapper) {
+      $bundles = $entity->getDataDefinition()->getConstraint('Bundle');
+      $bundle = $bundles ? array_shift($bundles) : 0;
+      if ($bundle && !in_array($bundle, $constraint->getBundleOption())) {
+        $this->context->addViolation($constraint->message, array('%bundle' => implode(', ', $constraint->getBundleOption())));
+      }
+      // $entity is not an entity, so we should not continue.
+      return;
+    }
+
     if (!in_array($entity->bundle(), $constraint->getBundleOption())) {
       $this->context->addViolation($constraint->message, ['%bundle' => implode(', ', $constraint->getBundleOption())]);
     }
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php
index b1fee2a..35c8f2b 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
 
+use Drupal\Core\Entity\EntityInterface;
 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\ConstraintValidator;
 
@@ -14,8 +15,8 @@ class EntityChangedConstraintValidator extends ConstraintValidator {
    * {@inheritdoc}
    */
   public function validate($entity, Constraint $constraint) {
-    if (isset($entity)) {
-      /** @var \Drupal\Core\Entity\EntityInterface $entity */
+    // Non-entities can be passed and should not be validated.
+    if (isset($entity) && $entity instanceof EntityInterface) {
       if (!$entity->isNew()) {
         $saved_entity = \Drupal::entityManager()->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id());
         // A change to any other translation must add a violation to the current
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
index 89c5e8e..ddfbeb6 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
 
+use Drupal\Core\Plugin\Plugin\DataType\DataTypeWrapper;
 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\ConstraintValidator;
 
@@ -18,6 +19,15 @@ public function validate($entity, Constraint $constraint) {
       return;
     }
 
+    // Validate against wrapper EntityType constraint when necessary.
+    if ($entity instanceof DataTypeWrapper) {
+      if ($entity->getDataDefinition()->getConstraint('EntityType') != $constraint->type) {
+        $this->context->addViolation($constraint->message, array('%type' => $constraint->type));
+        // $entity is not an entity, so we should not continue.
+        return;
+      }
+    }
+
     /** @var $entity \Drupal\Core\Entity\EntityInterface */
     if ($entity->getEntityTypeId() != $constraint->type) {
       $this->context->addViolation($constraint->message, ['%type' => $constraint->type]);
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
index 2a13d36..b5fa2db 100644
--- a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
@@ -4,7 +4,9 @@
 
 use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
+use Drupal\Core\TypedData\TypedDataManagerInterface;
 
 /**
  * Provides methods to handle sets of contexts.
@@ -12,6 +14,22 @@
 class ContextHandler implements ContextHandlerInterface {
 
   /**
+   * The typed data manager.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManagerInterface
+   */
+  protected $typedDataManager;
+
+  /**
+   * ContextHandler constructor.
+   *
+   * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager
+   */
+  public function __construct(TypedDataManagerInterface $typed_data_manager) {
+    $this->typedDataManager = $typed_data_manager;
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function filterPluginDefinitionsByContexts(array $contexts, array $definitions) {
@@ -51,11 +69,26 @@ public function getMatchingContexts(array $contexts, ContextDefinitionInterface
         return FALSE;
       }
 
-      // If any constraint does not match, this context is invalid.
-      foreach ($definition->getConstraints() as $constraint_name => $constraint) {
-        if ($context_definition->getConstraint($constraint_name) != $constraint) {
-          return FALSE;
+      // If the definition has constraints, validate the contexts against them.
+      if ($definition->getConstraints()) {
+        $constraints = [];
+        foreach ($definition->getConstraints() as $constraint_name => $constraint_settings) {
+          $constraints[] = $this->typedDataManager->getValidationConstraintManager()->create($constraint_name, $constraint_settings);
+        }
+        if ($context->hasContextValue()) {
+          $typed_data_definition = $context->getContextData();
+        }
+        else {
+          $typed_data_data_definition = $this->typedDataManager->createDataDefinition('data_type_wrapper');
+          foreach ($context_definition->getDataDefinition()->getConstraints() as $constraint => $constraint_settings) {
+            $typed_data_data_definition->addConstraint($constraint, $constraint_settings);
+          }
+          $typed_data_definition = $this->typedDataManager->create($typed_data_data_definition);
         }
+        $validator = $this->typedDataManager->getValidator();
+        $violations = $validator->validate($typed_data_definition, $constraints);
+        // Contexts without any violations are valid.
+        return !$violations->count();
       }
 
       // All contexts with matching data type and contexts are valid.
diff --git a/core/lib/Drupal/Core/Plugin/Plugin/DataType/DataTypeWrapper.php b/core/lib/Drupal/Core/Plugin/Plugin/DataType/DataTypeWrapper.php
new file mode 100644
index 0000000..171bed6
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Plugin/DataType/DataTypeWrapper.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\Plugin\DataType\DataTypeWrapper.
+ */
+
+namespace Drupal\Core\Plugin\Plugin\DataType;
+
+
+use Drupal\Core\TypedData\TypedData;
+
+/**
+ * Defines a generic "wrapper" data type.
+ *
+ * Instances of this class are used to validate constraints against each other
+ * when a raw data object is not available to validate against.
+ *
+ * The plugin system ContextHandler class manually builds instances of this
+ * data type when a value is not passed with a context, but a context is
+ * available. This allows the ContextHandler to determine what contexts will be
+ * valid during run time based upon the available administrative information.
+ *
+ * @see \Drupal\Core\Plugin\Context\ContextHandler
+ *
+ * @DataType(
+ *   id = "data_type_wrapper",
+ *   label = @Translation("Wrapper for validating sets of constraints against settings instead of objects."),
+ *   unwrap_for_canonical_representation = FALSE
+ * )
+ */
+class DataTypeWrapper extends TypedData {}
diff --git a/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php
index 4ed5e6e..78c943d 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php
@@ -1,26 +1,22 @@
 <?php
 
-/**
- * @file
- * Contains \Drupal\Tests\Core\Plugin\ContextHandlerTest.
- */
-
 namespace Drupal\Tests\Core\Plugin;
 
 use Drupal\Component\Plugin\ConfigurablePluginInterface;
 use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Core\Plugin\Context\ContextDefinition;
-use Drupal\Core\Plugin\Context\ContextHandler;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
 use Drupal\Core\TypedData\DataDefinition;
 use Drupal\Core\TypedData\Plugin\DataType\StringData;
-use Drupal\Tests\UnitTestCase;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
 
 /**
  * @coversDefaultClass \Drupal\Core\Plugin\Context\ContextHandler
  * @group Plugin
  */
-class ContextHandlerTest extends UnitTestCase {
+class ContextHandlerTest extends KernelTestBase {
+  use ContentTypeCreationTrait;
 
   /**
    * The context handler.
@@ -30,12 +26,25 @@ class ContextHandlerTest extends UnitTestCase {
   protected $contextHandler;
 
   /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['user', 'system', 'node', 'field', 'text', 'filter'];
+
+  /**
    * {@inheritdoc}
    */
   protected function setUp() {
     parent::setUp();
 
-    $this->contextHandler = new ContextHandler();
+    $this->installEntitySchema('user');
+    $this->installEntitySchema('node_type');
+    $this->installEntitySchema('node');
+    $this->installConfig('node');
+    $this->createContentType(['type' => 'page', 'name' => 'Page']);
+    $this->createContentType(['type' => 'article', 'name' => 'Article']);
+    $this->contextHandler = $this->container->get('context.handler');
   }
 
   /**
@@ -57,29 +66,21 @@ public function providerTestCheckRequirements() {
     $requirement_any = new ContextDefinition();
     $requirement_any->setRequired(TRUE);
 
-    $context_any = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
-    $context_any->expects($this->atLeastOnce())
-      ->method('getContextDefinition')
-      ->will($this->returnValue(new ContextDefinition('empty')));
-
-    $requirement_specific = new ContextDefinition('specific');
-    $requirement_specific->setConstraints(['bar' => 'baz']);
-
-    $context_constraint_mismatch = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
-    $context_constraint_mismatch->expects($this->atLeastOnce())
-      ->method('getContextDefinition')
-      ->will($this->returnValue(new ContextDefinition('foo')));
-    $context_datatype_mismatch = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
-    $context_datatype_mismatch->expects($this->atLeastOnce())
-      ->method('getContextDefinition')
-      ->will($this->returnValue(new ContextDefinition('fuzzy')));
-
-    $context_definition_specific = new ContextDefinition('specific');
-    $context_definition_specific->setConstraints(['bar' => 'baz']);
-    $context_specific = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
-    $context_specific->expects($this->atLeastOnce())
-      ->method('getContextDefinition')
-      ->will($this->returnValue($context_definition_specific));
+    $context_any = new Context(new ContextDefinition('any'));
+
+    $requirement_specific = new ContextDefinition('entity:node');
+    $requirement_specific->setConstraints(array('Bundle' => [0 => 'page']));
+
+    $mismatch_def = new ContextDefinition('entity:node');
+    $context_constraint_mismatch = new Context($mismatch_def->setConstraints(array('Bundle' => [0 => 'article'])));
+
+    $context_datatype_mismatch = new Context(new ContextDefinition('entity:user'));
+
+    $specific_def = new ContextDefinition('entity:node');
+    $context_specific = new Context($specific_def->setConstraints(array('Bundle' => [0 => 'page'])));
+
+    $requirement_complex = new ContextDefinition('entity:node');
+    $requirement_complex->setConstraints(['Bundle' => [0 => 'page', 1 => 'article']]);
 
     $data = [];
     $data[] = [[], [], TRUE];
@@ -90,6 +91,8 @@ public function providerTestCheckRequirements() {
     $data[] = [[$context_constraint_mismatch], [$requirement_specific], FALSE];
     $data[] = [[$context_datatype_mismatch], [$requirement_specific], FALSE];
     $data[] = [[$context_specific], [$requirement_specific], TRUE];
+    $data[] = array(array($context_specific), array($requirement_complex), TRUE);
+    $data[] = array(array($context_constraint_mismatch), array($requirement_complex), TRUE);
 
     return $data;
   }
@@ -103,7 +106,7 @@ public function testGetMatchingContexts($contexts, $requirement, $expected = NUL
     if (is_null($expected)) {
       $expected = $contexts;
     }
-    $this->assertSame($expected, $this->contextHandler->getMatchingContexts($contexts, $requirement));
+    $this->assertEquals($expected, $this->contextHandler->getMatchingContexts($contexts, $requirement));
   }
 
   /**
@@ -112,27 +115,21 @@ public function testGetMatchingContexts($contexts, $requirement, $expected = NUL
   public function providerTestGetMatchingContexts() {
     $requirement_any = new ContextDefinition();
 
-    $requirement_specific = new ContextDefinition('specific');
-    $requirement_specific->setConstraints(['bar' => 'baz']);
-
-    $context_any = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
-    $context_any->expects($this->atLeastOnce())
-      ->method('getContextDefinition')
-      ->will($this->returnValue(new ContextDefinition('empty')));
-    $context_constraint_mismatch = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
-    $context_constraint_mismatch->expects($this->atLeastOnce())
-      ->method('getContextDefinition')
-      ->will($this->returnValue(new ContextDefinition('foo')));
-    $context_datatype_mismatch = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
-    $context_datatype_mismatch->expects($this->atLeastOnce())
-      ->method('getContextDefinition')
-      ->will($this->returnValue(new ContextDefinition('fuzzy')));
-    $context_definition_specific = new ContextDefinition('specific');
-    $context_definition_specific->setConstraints(['bar' => 'baz']);
-    $context_specific = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
-    $context_specific->expects($this->atLeastOnce())
-      ->method('getContextDefinition')
-      ->will($this->returnValue($context_definition_specific));
+    $requirement_specific = new ContextDefinition('entity:node');
+    $requirement_specific->setConstraints(array('Bundle' => [0 => 'page']));
+
+    $context_any = new Context(new ContextDefinition());
+
+    $definition_constraint_mismatch = new ContextDefinition('entity:node');
+    $definition_constraint_mismatch->setConstraints(array('Bundle' => [0 => 'article']));
+    $context_constraint_mismatch = new Context($definition_constraint_mismatch);
+
+    $definition_datatype_mismatch = new ContextDefinition('entity:user');
+    $context_datatype_mismatch = new Context($definition_datatype_mismatch);
+
+    $context_definition_specific = new ContextDefinition('entity:node');
+    $context_definition_specific->setConstraints(array('Bundle' => [0 => 'page']));
+    $context_specific = new Context($context_definition_specific);
 
     $data = [];
     // No context will return no valid contexts.
@@ -155,19 +152,7 @@ public function providerTestGetMatchingContexts() {
    *
    * @dataProvider providerTestFilterPluginDefinitionsByContexts
    */
-  public function testFilterPluginDefinitionsByContexts($has_context, $definitions, $expected) {
-    if ($has_context) {
-      $context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
-      $expected_context_definition = (new ContextDefinition('expected_data_type'))->setConstraints(['expected_constraint_name' => 'expected_constraint_value']);
-      $context->expects($this->atLeastOnce())
-        ->method('getContextDefinition')
-        ->will($this->returnValue($expected_context_definition));
-      $contexts = [$context];
-    }
-    else {
-      $contexts = [];
-    }
-
+  public function testFilterPluginDefinitionsByContexts($contexts, $definitions, $expected) {
     $this->assertSame($expected, $this->contextHandler->filterPluginDefinitionsByContexts($contexts, $definitions));
   }
 
@@ -177,53 +162,61 @@ public function testFilterPluginDefinitionsByContexts($has_context, $definitions
   public function providerTestFilterPluginDefinitionsByContexts() {
     $data = [];
 
-    $plugins = [];
-    // No context and no plugins, no plugins available.
-    $data[] = [FALSE, $plugins, []];
-
-    $plugins = ['expected_plugin' => []];
-    // No context, all plugins available.
-    $data[] = [FALSE, $plugins, $plugins];
-
-    $plugins = ['expected_plugin' => ['context' => []]];
-    // No context, all plugins available.
-    $data[] = [FALSE, $plugins, $plugins];
-
-    $plugins = ['expected_plugin' => ['context' => ['context1' => new ContextDefinition('expected_data_type')]]];
-    // Missing context, no plugins available.
-    $data[] = [FALSE, $plugins, []];
-    // Satisfied context, all plugins available.
-    $data[] = [TRUE, $plugins, $plugins];
-
-    $mismatched_context_definition = (new ContextDefinition('expected_data_type'))->setConstraints(['mismatched_constraint_name' => 'mismatched_constraint_value']);
-    $plugins = ['expected_plugin' => ['context' => ['context1' => $mismatched_context_definition]]];
-    // Mismatched constraints, no plugins available.
-    $data[] = [TRUE, $plugins, []];
-
-    $optional_mismatched_context_definition = clone $mismatched_context_definition;
-    $optional_mismatched_context_definition->setRequired(FALSE);
-    $plugins = ['expected_plugin' => ['context' => ['context1' => $optional_mismatched_context_definition]]];
-    // Optional mismatched constraint, all plugins available.
-    $data[] = [FALSE, $plugins, $plugins];
-
-    $expected_context_definition = (new ContextDefinition('expected_data_type'))->setConstraints(['expected_constraint_name' => 'expected_constraint_value']);
-    $plugins = ['expected_plugin' => ['context' => ['context1' => $expected_context_definition]]];
-    // Satisfied context with constraint, all plugins available.
-    $data[] = [TRUE, $plugins, $plugins];
-
-    $optional_expected_context_definition = clone $expected_context_definition;
-    $optional_expected_context_definition->setRequired(FALSE);
-    $plugins = ['expected_plugin' => ['context' => ['context1' => $optional_expected_context_definition]]];
-    // Optional unsatisfied context, all plugins available.
-    $data[] = [FALSE, $plugins, $plugins];
-
-    $unexpected_context_definition = (new ContextDefinition('unexpected_data_type'))->setConstraints(['mismatched_constraint_name' => 'mismatched_constraint_value']);
+    $user = new ContextDefinition('entity:user');
+    $node = new ContextDefinition('entity:node');
+    $page = new ContextDefinition('entity:node');
+    $page->addConstraint('Bundle', [0 => 'page']);
+    $article = new ContextDefinition('entity:node');
+    $article->addConstraint('Bundle', [0 => 'article']);
+    $multi_bundle = clone ($node);
+    $multi_bundle->addConstraint('Bundle', [0 => 'page', 1 => 'article']);
     $plugins = [
-      'unexpected_plugin' => ['context' => ['context1' => $unexpected_context_definition]],
-      'expected_plugin' => ['context' => ['context2' => new ContextDefinition('expected_data_type')]],
+      'needs_node' => [
+        'context' => [
+          'node' => $node,
+        ],
+      ],
+      'needs_page' => [
+        'context' => [
+          'node' => $page,
+        ],
+      ],
+      'needs_article' => [
+        'context' => [
+          'node' => $article,
+        ],
+      ],
+      'needs_page_or_article' => [
+        'context' => [
+          'node' => $multi_bundle,
+        ],
+      ],
+      'needs_user' => [
+        'context' => [
+          'user' => $user,
+        ],
+      ],
+      'no_context_1' => [],
+      'no_context_2' => [],
     ];
-    // Context only satisfies one plugin.
-    $data[] = [TRUE, $plugins, ['expected_plugin' => $plugins['expected_plugin']]];
+    // No context, plugins without contextual requirements available.
+    $data[] = [[], $plugins, ['no_context_1' => $plugins['no_context_1'], 'no_context_2' => $plugins['no_context_2']]];
+    // Node context, plugins without contextual requirements and node specific
+    // contexts available.
+    $data[] = [['node' => new Context($node)], $plugins, ['needs_node' => $plugins['needs_node'], 'needs_page' => $plugins['needs_page'], 'needs_article' => $plugins['needs_article'], 'needs_page_or_article' => $plugins['needs_page_or_article'], 'no_context_1' => $plugins['no_context_1'], 'no_context_2' => $plugins['no_context_2']]];
+    // Node of bundle page contexts, plugins without contextual requirements and
+    // node or page specific bundle contexts available.
+    $data[] = [['page' => new Context($page)], $plugins, ['needs_node' => $plugins['needs_node'], 'needs_page' => $plugins['needs_page'], 'needs_page_or_article' => $plugins['needs_page_or_article'], 'no_context_1' => $plugins['no_context_1'], 'no_context_2' => $plugins['no_context_2']]];
+    // Node of bundle article contexts, plugins without contextual requirements
+    // and node or article specific bundle contexts available.
+    $data[] = [['article' => new Context($article)], $plugins, ['needs_node' => $plugins['needs_node'], 'needs_article' => $plugins['needs_article'], 'needs_page_or_article' => $plugins['needs_page_or_article'], 'no_context_1' => $plugins['no_context_1'], 'no_context_2' => $plugins['no_context_2']]];
+    // Nodes of bundles page and article contexts, plugins without contextual
+    // requirements and node, page or article specific bundle contexts
+    // available.
+    $data[] = [['page' => new Context($page), 'article' => new Context($article)], $plugins, ['needs_node' => $plugins['needs_node'], 'needs_page' => $plugins['needs_page'], 'needs_article' => $plugins['needs_article'], 'needs_page_or_article' => $plugins['needs_page_or_article'], 'no_context_1' => $plugins['no_context_1'], 'no_context_2' => $plugins['no_context_2']]];
+    // User context, plugins without contextual requirements and user specific
+    // contexts available.
+    $data[] = [['user' => new Context($user)], $plugins, ['needs_user' => $plugins['needs_user'], 'no_context_1' => $plugins['no_context_1'], 'no_context_2' => $plugins['no_context_2']]];
 
     return $data;
   }
