diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt
index 0739fb0..adc61a7 100644
--- a/core/MAINTAINERS.txt
+++ b/core/MAINTAINERS.txt
@@ -123,6 +123,7 @@ Color
 - ?
 
 Comment
+- Dick Olsson 'dixon_' https://www.drupal.org/u/dixon_
 - Lee Rowlands 'larowlan' https://www.drupal.org/u/larowlan
 - Andrey Postnikov 'andypost' https://www.drupal.org/u/andypost
 
diff --git a/core/composer.json b/core/composer.json
index 71f07d0..99b5cd0 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -142,8 +142,7 @@
         "drupal/update": "self.version",
         "drupal/user": "self.version",
         "drupal/views": "self.version",
-        "drupal/views_ui": "self.version",
-        "drupal/workflows": "self.version"
+        "drupal/views_ui": "self.version"
     },
     "minimum-stability": "dev",
     "prefer-stable": true,
diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index 9cfd1cf..ce12db5 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -46,7 +46,6 @@ drupal:
   version: VERSION
   js:
     misc/drupal.js: { weight: -18 }
-    misc/drupal.init.js: { weight: -17 }
   dependencies:
     - core/domready
     - core/drupalSettings
@@ -204,13 +203,6 @@ drupal.dropbutton:
     - core/drupalSettings
     - core/jquery.once
 
-drupal.entity-form:
-  version: VERSION
-  js:
-    misc/entity-form.js: {}
-  dependencies:
-    - core/drupal.form
-
 drupal.form:
   version: VERSION
   js:
diff --git a/core/core.services.yml b/core/core.services.yml
index c2842a7..5b0de75 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1235,6 +1235,11 @@ services:
     tags:
       - { name: event_subscriber }
     arguments: ['@logger.factory']
+  exception.custom_page_json:
+    class: Drupal\Core\EventSubscriber\ExceptionJsonSubscriber
+    tags:
+      - { name: event_subscriber }
+    arguments: ['@config.factory', '@path.alias_manager', '@http_kernel']
   exception.custom_page_html:
     class: Drupal\Core\EventSubscriber\CustomPageExceptionHtmlSubscriber
     tags:
@@ -1554,7 +1559,7 @@ services:
     class: Drupal\Core\Extension\InfoParser
   twig:
     class: Drupal\Core\Template\TwigEnvironment
-    arguments: ['@app.root', '@cache.default', '%twig_extension_hash%', '@state', '@twig.loader', '%twig.config%']
+    arguments: ['@app.root', '@cache.default', '%twig_extension_hash%', '@twig.loader', '%twig.config%']
     tags:
       - { name: service_collector, tag: 'twig.extension', call: addExtension }
   twig.extension:
diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php
index 4d3ea4e..81c72db 100644
--- a/core/lib/Drupal.php
+++ b/core/lib/Drupal.php
@@ -573,7 +573,8 @@ public static function linkGenerator() {
    * Renders a link with a given link text and Url object.
    *
    * This method is a convenience wrapper for the link generator service's
-   * generate() method.
+   * generate() method. For detailed documentation, see
+   * \Drupal\Core\Routing\LinkGeneratorInterface::generate().
    *
    * @param string $text
    *   The link text for the anchor tag.
diff --git a/core/lib/Drupal/Component/Annotation/Plugin/Discovery/AnnotatedClassDiscovery.php b/core/lib/Drupal/Component/Annotation/Plugin/Discovery/AnnotatedClassDiscovery.php
index 879f13b..e564fd0 100644
--- a/core/lib/Drupal/Component/Annotation/Plugin/Discovery/AnnotatedClassDiscovery.php
+++ b/core/lib/Drupal/Component/Annotation/Plugin/Discovery/AnnotatedClassDiscovery.php
@@ -10,7 +10,6 @@
 use Doctrine\Common\Annotations\AnnotationRegistry;
 use Doctrine\Common\Reflection\StaticReflectionParser;
 use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
-use Drupal\Component\Utility\Crypt;
 
 /**
  * Defines a discovery mechanism to find annotated plugins in PSR-0 namespaces.
@@ -75,7 +74,7 @@ function __construct($plugin_namespaces = array(), $plugin_definition_annotation
     $this->annotationNamespaces = $annotation_namespaces;
 
     $file_cache_suffix = str_replace('\\', '_', $plugin_definition_annotation_name);
-    $file_cache_suffix .= ':' . Crypt::hashBase64(serialize($annotation_namespaces));
+    $file_cache_suffix .= ':' . hash('crc32b', serialize($annotation_namespaces));
     $this->fileCache = FileCacheFactory::get('annotation_discovery:' . $file_cache_suffix);
   }
 
diff --git a/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php
index 76a43fd..984ec50 100644
--- a/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php
+++ b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Component\DependencyInjection\Dumper;
 
-use Drupal\Component\Utility\Crypt;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\DependencyInjection\Definition;
 use Symfony\Component\DependencyInjection\Parameter;
@@ -374,7 +373,7 @@ protected function dumpCallable($callable) {
   protected function getPrivateServiceCall($id, Definition $definition, $shared = FALSE) {
     $service_definition = $this->getServiceDefinition($definition);
     if (!$id) {
-      $hash = Crypt::hashBase64(serialize($service_definition));
+      $hash = hash('sha1', serialize($service_definition));
       $id = 'private__' . $hash;
     }
     return (object) array(
diff --git a/core/lib/Drupal/Component/Utility/Xss.php b/core/lib/Drupal/Component/Utility/Xss.php
index ab8be78..d9f24b4 100644
--- a/core/lib/Drupal/Component/Utility/Xss.php
+++ b/core/lib/Drupal/Component/Utility/Xss.php
@@ -224,6 +224,8 @@ protected static function attributes($attributes) {
             $skip_protocol_filtering = substr($attribute_name, 0, 5) === 'data-' || in_array($attribute_name, array(
               'title',
               'alt',
+              'rel',
+              'property',
             ));
 
             $working = $mode = 1;
diff --git a/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php b/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
index d529c0c..ce0a261 100644
--- a/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
+++ b/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Core\Access;
 
-use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
 use Symfony\Component\Routing\Route;
@@ -46,7 +45,7 @@ public function processOutbound($route_name, Route $route, array &$parameters, B
       }
       else {
         // Generate a placeholder and a render array to replace it.
-        $placeholder = Crypt::hashBase64($path);
+        $placeholder = hash('sha1', $path);
         $placeholder_render_array = [
           '#lazy_builder' => ['route_processor_csrf:renderPlaceholderCsrfToken', [$path]],
         ];
diff --git a/core/lib/Drupal/Core/Config/Development/ConfigSchemaChecker.php b/core/lib/Drupal/Core/Config/Development/ConfigSchemaChecker.php
index e639966..2ea20ff 100644
--- a/core/lib/Drupal/Core/Config/Development/ConfigSchemaChecker.php
+++ b/core/lib/Drupal/Core/Config/Development/ConfigSchemaChecker.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Core\Config\Development;
 
-use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Config\ConfigCrudEvent;
 use Drupal\Core\Config\ConfigEvents;
@@ -80,7 +79,7 @@ public function onConfigSave(ConfigCrudEvent $event) {
 
     $name = $saved_config->getName();
     $data = $saved_config->get();
-    $checksum = Crypt::hashBase64(serialize($data));
+    $checksum = hash('crc32b', serialize($data));
     if (!in_array($name, $this->exclude) && !isset($this->checked[$name . ':' . $checksum])) {
       $this->checked[$name . ':' . $checksum] = TRUE;
       $errors = $this->checkConfigSchema($this->typedManager, $name, $data);
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/TwigExtensionPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/TwigExtensionPass.php
index 73ee17b..d5c7931 100644
--- a/core/lib/Drupal/Core/DependencyInjection/Compiler/TwigExtensionPass.php
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/TwigExtensionPass.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Core\DependencyInjection\Compiler;
 
-use Drupal\Component\Utility\Crypt;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 
@@ -27,7 +26,7 @@ public function process(ContainerBuilder $container) {
       $twig_extension_hash .= $class_name . filemtime($reflection->getFileName());
     }
 
-    $container->setParameter('twig_extension_hash', Crypt::hashBase64($twig_extension_hash));
+    $container->setParameter('twig_extension_hash', hash('crc32b', $twig_extension_hash));
   }
 
 }
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityForm.php b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
index e131e9b..29b0a9b 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityForm.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Core\Entity;
 
-use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
 use Drupal\Core\Entity\Entity\EntityFormDisplay;
 use Drupal\Core\Form\FormStateInterface;
@@ -23,41 +22,13 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface
   protected $entityManager;
 
   /**
-   * The entity being used by this form.
-   *
-   * @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\Core\Entity\RevisionLogInterface
-   */
-  protected $entity;
-
-  /**
-   * The entity type bundle info service.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
-   */
-  protected $entityTypeBundleInfo;
-
-  /**
-   * The time service.
-   *
-   * @var \Drupal\Component\Datetime\TimeInterface
-   */
-  protected $time;
-
-  /**
    * Constructs a ContentEntityForm object.
    *
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
-   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
-   *   The entity type bundle service.
-   * @param \Drupal\Component\Datetime\TimeInterface $time
-   *   The time service.
    */
-  public function __construct(EntityManagerInterface $entity_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
+  public function __construct(EntityManagerInterface $entity_manager) {
     $this->entityManager = $entity_manager;
-
-    $this->entityTypeBundleInfo = $entity_type_bundle_info ?: \Drupal::service('entity_type.bundle.info');
-    $this->time = $time ?: \Drupal::service('datetime.time');
   }
 
   /**
@@ -65,54 +36,15 @@ public function __construct(EntityManagerInterface $entity_manager, EntityTypeBu
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('entity.manager'),
-      $container->get('entity_type.bundle.info'),
-      $container->get('datetime.time')
+      $container->get('entity.manager')
     );
   }
 
   /**
    * {@inheritdoc}
    */
-  protected function prepareEntity() {
-    parent::prepareEntity();
-
-    // Hide the current revision log message in UI.
-    if ($this->showRevisionUi() && !$this->entity->isNew()) {
-      $this->entity->setRevisionLogMessage(NULL);
-    }
-  }
-
-  /**
-   * Returns the bundle entity of the entity, or NULL if there is none.
-   *
-   * @return \Drupal\Core\Entity\EntityInterface|null
-   *   The bundle entity.
-   */
-  protected function getBundleEntity() {
-    if ($bundle_entity_type = $this->entity->getEntityType()->getBundleEntityType()) {
-      return $this->entityTypeManager->getStorage($bundle_entity_type)->load($this->entity->bundle());
-    }
-    return NULL;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function form(array $form, FormStateInterface $form_state) {
-
-    if ($this->showRevisionUi()) {
-      // Advanced tab must be the first, because other fields rely on that.
-      if (!isset($form['advanced'])) {
-        $form['advanced'] = [
-          '#type' => 'vertical_tabs',
-          '#weight' => 99,
-        ];
-      }
-    }
-
     $form = parent::form($form, $form_state);
-
     // Content entity forms do not use the parent's #after_build callback
     // because they only need to rebuild the entity in the validation and the
     // submit handler because Field API uses its own #after_build callback for
@@ -122,11 +54,6 @@ public function form(array $form, FormStateInterface $form_state) {
     $this->getFormDisplay($form_state)->buildForm($this->entity, $form, $form_state);
     // Allow modules to act before and after form language is updated.
     $form['#entity_builders']['update_form_langcode'] = '::updateFormLangcode';
-
-    if ($this->showRevisionUi()) {
-      $this->addRevisionableFormFields($form);
-    }
-
     return $form;
   }
 
@@ -149,17 +76,6 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
     // Mark the entity as requiring validation.
     $entity->setValidationRequired(!$form_state->getTemporaryValue('entity_validated'));
 
-    // Save as a new revision if requested to do so.
-    if ($this->showRevisionUi() && !$form_state->isValueEmpty('revision')) {
-      $entity->setNewRevision();
-      if ($entity instanceof RevisionLogInterface) {
-        // If a new revision is created, save the current user as
-        // revision author.
-        $entity->setRevisionUserId($this->currentUser()->id());
-        $entity->setRevisionCreationTime($this->time->getRequestTime());
-      }
-    }
-
     return $entity;
   }
 
@@ -367,84 +283,8 @@ public function updateFormLangcode($entity_type_id, EntityInterface $entity, arr
    */
   public function updateChangedTime(EntityInterface $entity) {
     if ($entity->getEntityType()->isSubclassOf(EntityChangedInterface::class)) {
-      $entity->setChangedTime($this->time->getRequestTime());
+      $entity->setChangedTime(REQUEST_TIME);
     }
   }
 
-  /**
-   * Add revision form fields if the entity enabled the UI.
-   *
-   * @param array $form
-   *   An associative array containing the structure of the form.
-   */
-  protected function addRevisionableFormFields(array &$form) {
-    $entity_type = $this->entity->getEntityType();
-
-    $new_revision_default = $this->getNewRevisionDefault();
-
-    // Add a log field if the "Create new revision" option is checked, or if the
-    // current user has the ability to check that option.
-    $form['revision_information'] = [
-      '#type' => 'details',
-      '#title' => $this->t('Revision information'),
-      // Open by default when "Create new revision" is checked.
-      '#open' => $new_revision_default,
-      '#group' => 'advanced',
-      '#weight' => 20,
-      '#access' => $new_revision_default || $this->entity->get($entity_type->getKey('revision'))->access('update'),
-      '#optional' => TRUE,
-      '#attributes' => [
-        'class' => ['entity-content-form-revision-information'],
-      ],
-      '#attached' => [
-        'library' => ['core/drupal.entity-form'],
-      ],
-    ];
-
-    $form['revision'] = [
-      '#type' => 'checkbox',
-      '#title' => $this->t('Create new revision'),
-      '#default_value' => $new_revision_default,
-      '#access' => !$this->entity->isNew() && $this->entity->get($entity_type->getKey('revision'))->access('update'),
-      '#group' => 'revision_information',
-    ];
-
-    if (isset($form['revision_log'])) {
-      $form['revision_log'] += [
-        '#group' => 'revision_information',
-        '#states' => [
-          'visible' => [
-            ':input[name="revision"]' => ['checked' => TRUE],
-          ],
-        ],
-      ];
-    }
-  }
-
-  /**
-   * Should new revisions created on default.
-   *
-   * @return bool
-   *   New revision on default.
-   */
-  protected function getNewRevisionDefault() {
-    $new_revision_default = FALSE;
-    $bundle_entity = $this->getBundleEntity();
-    if ($bundle_entity instanceof RevisionableEntityBundleInterface) {
-      // Always use the default revision setting.
-      $new_revision_default = $bundle_entity->shouldCreateNewRevision();
-    }
-    return $new_revision_default;
-  }
-
-  /**
-   * Checks whether the revision form fields should be added to the form.
-   *
-   * @return bool
-   *   TRUE if the form field should be added, FALSE otherwise.
-   */
-  protected function showRevisionUi() {
-    return $this->entity->getEntityType()->showRevisionUi();
-  }
-
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php
index 78fb2ff..89b83cf 100644
--- a/core/lib/Drupal/Core/Entity/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -175,13 +175,6 @@ class EntityType implements EntityTypeInterface {
   protected $translatable = FALSE;
 
   /**
-   * Indicates whether the revision form fields should be added to the form.
-   *
-   * @var bool
-   */
-  protected $show_revision_ui = FALSE;
-
-  /**
    * The human-readable name of the type.
    *
    * @var string
@@ -699,13 +692,6 @@ public function getBaseTable() {
   /**
    * {@inheritdoc}
    */
-  public function showRevisionUi() {
-    return $this->isRevisionable() && $this->show_revision_ui;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function isTranslatable() {
     return !empty($this->translatable);
   }
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
index 60d60b5..c6336c9 100644
--- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
@@ -574,14 +574,6 @@ public function getBaseTable();
   public function isTranslatable();
 
   /**
-   * Indicates whether the revision form fields should be added to the form.
-   *
-   * @return bool
-   *   TRUE if the form field should be added, FALSE otherwise.
-   */
-  public function showRevisionUi();
-
-  /**
    * Indicates whether entities of this type have revision support.
    *
    * @return bool
diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index 1b3e6c0..7097b85 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Core\Entity;
 
-use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Entity\Entity\EntityViewDisplay;
@@ -463,7 +462,7 @@ protected function getSingleFieldDisplay($entity, $field_name, $display_options)
       // series of fields individually for cases such as views tables.
       $entity_type_id = $entity->getEntityTypeId();
       $bundle = $entity->bundle();
-      $key = $entity_type_id . ':' . $bundle . ':' . $field_name . ':' . Crypt::hashBase64(serialize($display_options));
+      $key = $entity_type_id . ':' . $bundle . ':' . $field_name . ':' . hash('crc32b', serialize($display_options));
       if (!isset($this->singleFieldDisplays[$key])) {
         $this->singleFieldDisplays[$key] = EntityViewDisplay::create(array(
           'targetEntityType' => $entity_type_id,
diff --git a/core/lib/Drupal/Core/Entity/RevisionableEntityBundleInterface.php b/core/lib/Drupal/Core/Entity/RevisionableEntityBundleInterface.php
deleted file mode 100644
index caf44f2..0000000
--- a/core/lib/Drupal/Core/Entity/RevisionableEntityBundleInterface.php
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-
-namespace Drupal\Core\Entity;
-
-use Drupal\Core\Config\Entity\ConfigEntityInterface;
-
-/**
- * Provides an interface defining a revisionable entity bundle.
- */
-interface RevisionableEntityBundleInterface extends ConfigEntityInterface {
-
-  /**
-   * Gets whether a new revision should be created by default.
-   *
-   * @return bool
-   *   TRUE if a new revision should be created by default.
-   */
-  public function shouldCreateNewRevision();
-
-}
diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
index 61da55f..f43461f 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -676,13 +676,13 @@ protected function getFieldUniqueKeys($field_name, array $field_schema, array $c
   protected function getFieldSchemaData($field_name, array $field_schema, array $column_mapping, $schema_key) {
     $data = array();
 
-    $entity_type_id = $this->entityType->id();
     foreach ($field_schema[$schema_key] as $key => $columns) {
       // To avoid clashes with entity-level indexes or unique keys we use
       // "{$entity_type_id}_field__" as a prefix instead of just
       // "{$entity_type_id}__". We additionally namespace the specifier by the
       // field name to avoid clashes when multiple fields of the same type are
       // added to an entity type.
+      $entity_type_id = $this->entityType->id();
       $real_key = $this->getFieldSchemaIdentifierName($entity_type_id, $field_name, $key);
       foreach ($columns as $column) {
         // Allow for indexes and unique keys to specified as an array of column
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index 803b4f1..878967b 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -683,9 +683,8 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
       // will be replaced at the very last moment. This ensures forms with
       // dynamically generated action URLs don't have poor cacheability.
       // Use the proper API to generate the placeholder, when we have one. See
-      // https://www.drupal.org/node/2562341. The placholder uses a fixed string
-      // that is Crypt::hashBase64('Drupal\Core\Form\FormBuilder::prepareForm');
-      $placeholder = 'form_action_p_pvdeGsVG5zNF_XLGPTvYSKCf43t8qZYSwcfZl2uzM';
+      // https://www.drupal.org/node/2562341.
+      $placeholder = 'form_action_' . hash('crc32b', __METHOD__);
 
       $form['#attached']['placeholders'][$placeholder] = [
         '#lazy_builder' => ['form_builder:renderPlaceholderFormAction', []],
@@ -745,7 +744,7 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
       if ($user && $user->isAuthenticated()) {
         // Generate a public token based on the form id.
         // Generates a placeholder based on the form ID.
-        $placeholder = 'form_token_placeholder_' . Crypt::hashBase64($form_id);
+        $placeholder = 'form_token_placeholder_' . hash('crc32b', $form_id);
         $form['#token'] = $placeholder;
 
         $form['form_token'] = array(
diff --git a/core/lib/Drupal/Core/Render/PlaceholderGenerator.php b/core/lib/Drupal/Core/Render/PlaceholderGenerator.php
index fad8cf7..488ab88 100644
--- a/core/lib/Drupal/Core/Render/PlaceholderGenerator.php
+++ b/core/lib/Drupal/Core/Render/PlaceholderGenerator.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Core\Render;
 
-use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Cache\Cache;
@@ -85,7 +84,7 @@ public function createPlaceholder(array $element) {
     // debugging.
     $callback = $placeholder_render_array['#lazy_builder'][0];
     $arguments = UrlHelper::buildQuery($placeholder_render_array['#lazy_builder'][1]);
-    $token = Crypt::hashBase64(serialize($placeholder_render_array));
+    $token = hash('crc32b', serialize($placeholder_render_array));
     $placeholder_markup = '<drupal-render-placeholder callback="' . Html::escape($callback) . '" arguments="' . Html::escape($arguments) . '" token="' . Html::escape($token) . '"></drupal-render-placeholder>';
 
     // Build the placeholder element to return.
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index a39b574..4bbffc3 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -365,11 +365,6 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
       $elements['#lazy_builder_built'] = TRUE;
     }
 
-    // All render elements support #markup and #plain_text.
-    if (!empty($elements['#markup']) || !empty($elements['#plain_text'])) {
-      $elements = $this->ensureMarkupIsSafe($elements);
-    }
-
     // Make any final changes to the element before it is rendered. This means
     // that the $element or the children can be altered or corrected before the
     // element is rendered into the final text.
@@ -382,6 +377,11 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
       }
     }
 
+    // All render elements support #markup and #plain_text.
+    if (!empty($elements['#markup']) || !empty($elements['#plain_text'])) {
+      $elements = $this->ensureMarkupIsSafe($elements);
+    }
+
     // Defaults for bubbleable rendering metadata.
     $elements['#cache']['tags'] = isset($elements['#cache']['tags']) ? $elements['#cache']['tags'] : array();
     $elements['#cache']['max-age'] = isset($elements['#cache']['max-age']) ? $elements['#cache']['max-age'] : Cache::PERMANENT;
diff --git a/core/lib/Drupal/Core/Render/theme.api.php b/core/lib/Drupal/Core/Render/theme.api.php
index 0dd7573..322761e 100644
--- a/core/lib/Drupal/Core/Render/theme.api.php
+++ b/core/lib/Drupal/Core/Render/theme.api.php
@@ -254,13 +254,13 @@
  * form array, which specifies the form elements for an HTML form; see the
  * @link form_api Form generation topic @endlink for more information on forms.
  *
- * Render arrays (at any level of the hierarchy) will usually have one of the
- * following properties defined:
+ * Render arrays (at each level in the hierarchy) will usually have one of the
+ * following three properties defined:
  * - #type: Specifies that the array contains data and options for a particular
- *   type of "render element" (for example, 'form', for an HTML form;
- *   'textfield', 'submit', for HTML form element types; 'table', for a table
- *   with rows, columns, and headers). See @ref elements below for more on
- *   render element types.
+ *   type of "render element" (examples: 'form', for an HTML form; 'textfield',
+ *   'submit', and other HTML form element types; 'table', for a table with
+ *   rows, columns, and headers). See @ref elements below for more on render
+ *   element types.
  * - #theme: Specifies that the array contains data to be themed by a particular
  *   theme hook. Modules define theme hooks by implementing hook_theme(), which
  *   specifies the input "variables" used to provide data and options; if a
@@ -277,29 +277,30 @@
  *   can customize the markup. Note that the value is passed through
  *   \Drupal\Component\Utility\Xss::filterAdmin(), which strips known XSS
  *   vectors while allowing a permissive list of HTML tags that are not XSS
- *   vectors. (For example, <script> and <style> are not allowed.) See
- *   \Drupal\Component\Utility\Xss::$adminTags for the list of allowed tags. If
- *   your markup needs any of the tags not in this whitelist, then you can
- *   implement a theme hook and/or an asset library. Alternatively, you can use
- *   the key #allowed_tags to alter which tags are filtered.
+ *   vectors. (I.e, <script> and <style> are not allowed.) See
+ *   \Drupal\Component\Utility\Xss::$adminTags for the list of tags that will
+ *   be allowed. If your markup needs any of the tags that are not in this
+ *   whitelist, then you can implement a theme hook and template file and/or
+ *   an asset library. Aternatively, you can use the render array key
+ *   #allowed_tags to alter which tags are filtered.
  * - #plain_text: Specifies that the array provides text that needs to be
- *   escaped. This value takes precedence over #markup.
- * - #allowed_tags: If #markup is supplied, this can be used to change which
- *   tags are allowed in the markup. The value is an array of tags that
- *   Xss::filter() would accept. If #plain_text is set, this value is ignored.
+ *   escaped. This value takes precedence over #markup if present.
+ * - #allowed_tags: If #markup is supplied this can be used to change which tags
+ *   are using to filter the markup. The value should be an array of tags that
+ *   Xss::filter() would accept. If #plain_text is set this value is ignored.
  *
  *   Usage example:
  *   @code
- *   $output['admin_filtered_string'] = [
+ *   $output['admin_filtered_string'] = array(
  *     '#markup' => '<em>This is filtered using the admin tag list</em>',
- *   ];
- *   $output['filtered_string'] = [
- *     '#markup' => '<video><source src="v.webm" type="video/webm"></video>',
- *     '#allowed_tags' => ['video', 'source'],
- *   ];
- *   $output['escaped_string'] = [
+ *   );
+ *   $output['filtered_string'] = array(
+ *     '#markup' => '<em>This is filtered</em>',
+ *     '#allowed_tags' => ['strong'],
+ *   );
+ *   $output['escaped_string'] = array(
  *     '#plain_text' => '<em>This is escaped</em>',
- *   ];
+ *   );
  *   @endcode
  *
  *   @see core.libraries.yml
diff --git a/core/lib/Drupal/Core/Template/TwigEnvironment.php b/core/lib/Drupal/Core/Template/TwigEnvironment.php
index f5ee459..fe391d8 100644
--- a/core/lib/Drupal/Core/Template/TwigEnvironment.php
+++ b/core/lib/Drupal/Core/Template/TwigEnvironment.php
@@ -4,7 +4,6 @@
 
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Render\Markup;
-use Drupal\Core\State\StateInterface;
 
 /**
  * A class that defines a Twig environment for Drupal.
@@ -23,8 +22,6 @@ class TwigEnvironment extends \Twig_Environment {
    */
   protected $templateClasses;
 
-  protected $twigCachePrefix = '';
-
   /**
    * Constructs a TwigEnvironment object and stores cache and storage
    * internally.
@@ -35,14 +32,12 @@ class TwigEnvironment extends \Twig_Environment {
    *   The cache bin.
    * @param string $twig_extension_hash
    *   The Twig extension hash.
-   * @param \Drupal\Core\State\StateInterface $state
-   *   The state service.
    * @param \Twig_LoaderInterface $loader
    *   The Twig loader or loader chain.
    * @param array $options
    *   The options for the Twig environment.
    */
-  public function __construct($root, CacheBackendInterface $cache, $twig_extension_hash, StateInterface $state, \Twig_LoaderInterface $loader = NULL, $options = array()) {
+  public function __construct($root, CacheBackendInterface $cache, $twig_extension_hash, \Twig_LoaderInterface $loader = NULL, $options = array()) {
     // Ensure that twig.engine is loaded, given that it is needed to render a
     // template because functions like TwigExtension::escapeFilter() are called.
     require_once $root . '/core/themes/engines/twig/twig.engine';
@@ -63,19 +58,7 @@ public function __construct($root, CacheBackendInterface $cache, $twig_extension
     $this->addExtension($sandbox);
 
     if ($options['cache'] === TRUE) {
-      $current = $state->get('twig_extension_hash_prefix', ['twig_extension_hash' => '']);
-      if ($current['twig_extension_hash'] !== $twig_extension_hash || empty($current['twig_cache_prefix'])) {
-        $current = [
-          'twig_extension_hash' => $twig_extension_hash,
-          // Generate a new prefix which invalidates any existing cached files.
-          'twig_cache_prefix' => uniqid(),
-
-        ];
-        $state->set('twig_extension_hash_prefix', $current);
-      }
-      $this->twigCachePrefix = $current['twig_cache_prefix'];
-
-      $options['cache'] = new TwigPhpStorageCache($cache, $this->twigCachePrefix);
+      $options['cache'] = new TwigPhpStorageCache($cache, $twig_extension_hash);
     }
 
     $this->loader = $loader;
@@ -83,16 +66,6 @@ public function __construct($root, CacheBackendInterface $cache, $twig_extension
   }
 
   /**
-   * Get the cache prefixed used by \Drupal\Core\Template\TwigPhpStorageCache
-   *
-   * @return string
-   *   The file cache prefix, or empty string if the cache is disabled.
-   */
-  public function getTwigCachePrefix() {
-    return $this->twigCachePrefix;
-  }
-
-  /**
    * Gets the template class associated with the given string.
    *
    * @param string $name
diff --git a/core/lib/Drupal/Core/Template/TwigPhpStorageCache.php b/core/lib/Drupal/Core/Template/TwigPhpStorageCache.php
index f99f8c5..b9f7cae 100644
--- a/core/lib/Drupal/Core/Template/TwigPhpStorageCache.php
+++ b/core/lib/Drupal/Core/Template/TwigPhpStorageCache.php
@@ -43,12 +43,12 @@ class TwigPhpStorageCache implements \Twig_CacheInterface {
    *
    * @param \Drupal\Core\Cache\CacheBackendInterface $cache
    *   The cache bin.
-   * @param string $twig_cache_prefix
-   *   A Twig cache file prefix that changes when Twig extensions change.
+   * @param string $twig_extension_hash
+   *   The Twig extension hash.
    */
-  public function __construct(CacheBackendInterface $cache, $twig_cache_prefix) {
+  public function __construct(CacheBackendInterface $cache, $twig_extension_hash) {
     $this->cache = $cache;
-    $this->templateCacheFilenamePrefix = $twig_cache_prefix;
+    $this->templateCacheFilenamePrefix = $twig_extension_hash;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php b/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php
deleted file mode 100644
index dbbd962..0000000
--- a/core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php
+++ /dev/null
@@ -1,452 +0,0 @@
-<?php
-
-namespace Drupal\Core\Test;
-
-use Drupal\Component\FileCache\FileCacheFactory;
-use Drupal\Component\Utility\SafeMarkup;
-use Drupal\Core\Cache\Cache;
-use Drupal\Core\Config\Development\ConfigSchemaChecker;
-use Drupal\Core\DrupalKernel;
-use Drupal\Core\Extension\MissingDependencyException;
-use Drupal\Core\Serialization\Yaml;
-use Drupal\Core\Session\UserSession;
-use Drupal\Core\Site\Settings;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\Yaml\Yaml as SymfonyYaml;
-
-/**
- * Defines a trait for shared functional test setup functionality.
- */
-trait FunctionalTestSetupTrait {
-
-  /**
-   * The "#1" admin user.
-   *
-   * @var \Drupal\Core\Session\AccountInterface
-   */
-  protected $rootUser;
-
-  /**
-   * The class loader to use for installation and initialization of setup.
-   *
-   * @var \Symfony\Component\Classloader\Classloader
-   */
-  protected $classLoader;
-
-  /**
-   * The config directories used in this test.
-   */
-  protected $configDirectories = array();
-
-  /**
-   * Prepares site settings and services before installation.
-   */
-  protected function prepareSettings() {
-    // Prepare installer settings that are not install_drupal() parameters.
-    // Copy and prepare an actual settings.php, so as to resemble a regular
-    // installation.
-    // Not using File API; a potential error must trigger a PHP warning.
-    $directory = DRUPAL_ROOT . '/' . $this->siteDirectory;
-    copy(DRUPAL_ROOT . '/sites/default/default.settings.php', $directory . '/settings.php');
-
-    // All file system paths are created by System module during installation.
-    // @see system_requirements()
-    // @see TestBase::prepareEnvironment()
-    $settings['settings']['file_public_path'] = (object) [
-      'value' => $this->publicFilesDirectory,
-      'required' => TRUE,
-    ];
-    $settings['settings']['file_private_path'] = (object) [
-      'value' => $this->privateFilesDirectory,
-      'required' => TRUE,
-    ];
-    // Save the original site directory path, so that extensions in the
-    // site-specific directory can still be discovered in the test site
-    // environment.
-    // @see \Drupal\Core\Extension\ExtensionDiscovery::scan()
-    $settings['settings']['test_parent_site'] = (object) [
-      'value' => $this->originalSite,
-      'required' => TRUE,
-    ];
-    // Add the parent profile's search path to the child site's search paths.
-    // @see \Drupal\Core\Extension\ExtensionDiscovery::getProfileDirectories()
-    $settings['conf']['simpletest.settings']['parent_profile'] = (object) [
-      'value' => $this->originalProfile,
-      'required' => TRUE,
-    ];
-    $this->writeSettings($settings);
-    // Allow for test-specific overrides.
-    $settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSite . '/settings.testing.php';
-    if (file_exists($settings_testing_file)) {
-      // Copy the testing-specific settings.php overrides in place.
-      copy($settings_testing_file, $directory . '/settings.testing.php');
-      // Add the name of the testing class to settings.php and include the
-      // testing specific overrides.
-      file_put_contents($directory . '/settings.php', "\n\$test_class = '" . get_class($this) . "';\n" . 'include DRUPAL_ROOT . \'/\' . $site_path . \'/settings.testing.php\';' . "\n", FILE_APPEND);
-    }
-    $settings_services_file = DRUPAL_ROOT . '/' . $this->originalSite . '/testing.services.yml';
-    if (!file_exists($settings_services_file)) {
-      // Otherwise, use the default services as a starting point for overrides.
-      $settings_services_file = DRUPAL_ROOT . '/sites/default/default.services.yml';
-    }
-    // Copy the testing-specific service overrides in place.
-    copy($settings_services_file, $directory . '/services.yml');
-    if ($this->strictConfigSchema) {
-      // Add a listener to validate configuration schema on save.
-      $yaml = new SymfonyYaml();
-      $content = file_get_contents($directory . '/services.yml');
-      $services = $yaml->parse($content);
-      $services['services']['simpletest.config_schema_checker'] = [
-        'class' => ConfigSchemaChecker::class,
-        'arguments' => ['@config.typed', $this->getConfigSchemaExclusions()],
-        'tags' => [['name' => 'event_subscriber']],
-      ];
-      file_put_contents($directory . '/services.yml', $yaml->dump($services));
-    }
-    // Since Drupal is bootstrapped already, install_begin_request() will not
-    // bootstrap again. Hence, we have to reload the newly written custom
-    // settings.php manually.
-    Settings::initialize(DRUPAL_ROOT, $this->siteDirectory, $this->classLoader);
-  }
-
-  /**
-   * Rewrites the settings.php file of the test site.
-   *
-   * @param array $settings
-   *   An array of settings to write out, in the format expected by
-   *   drupal_rewrite_settings().
-   *
-   * @see drupal_rewrite_settings()
-   */
-  protected function writeSettings(array $settings) {
-    include_once DRUPAL_ROOT . '/core/includes/install.inc';
-    $filename = $this->siteDirectory . '/settings.php';
-    // system_requirements() removes write permissions from settings.php
-    // whenever it is invoked.
-    // Not using File API; a potential error must trigger a PHP warning.
-    chmod($filename, 0666);
-    drupal_rewrite_settings($settings, $filename);
-  }
-
-  /**
-   * Changes parameters in the services.yml file.
-   *
-   * @param string $name
-   *   The name of the parameter.
-   * @param string $value
-   *   The value of the parameter.
-   */
-  protected function setContainerParameter($name, $value) {
-    $filename = $this->siteDirectory . '/services.yml';
-    chmod($filename, 0666);
-
-    $services = Yaml::decode(file_get_contents($filename));
-    $services['parameters'][$name] = $value;
-    file_put_contents($filename, Yaml::encode($services));
-
-    // Ensure that the cache is deleted for the yaml file loader.
-    $file_cache = FileCacheFactory::get('container_yaml_loader');
-    $file_cache->delete($filename);
-  }
-
-  /**
-   * Rebuilds \Drupal::getContainer().
-   *
-   * Use this to update the test process's kernel with a new service container.
-   * For example, when the list of enabled modules is changed via the internal
-   * browser the test process's kernel has a service container with an out of
-   * date module list.
-   *
-   * @see TestBase::prepareEnvironment()
-   * @see TestBase::restoreEnvironment()
-   *
-   * @todo Fix https://www.drupal.org/node/2021959 so that module enable/disable
-   *   changes are immediately reflected in \Drupal::getContainer(). Until then,
-   *   tests can invoke this workaround when requiring services from newly
-   *   enabled modules to be immediately available in the same request.
-   */
-  protected function rebuildContainer() {
-    // Rebuild the kernel and bring it back to a fully bootstrapped state.
-    $this->container = $this->kernel->rebuildContainer();
-
-    // Make sure the url generator has a request object, otherwise calls to
-    // $this->drupalGet() will fail.
-    $this->prepareRequestForGenerator();
-  }
-
-  /**
-   * Resets all data structures after having enabled new modules.
-   *
-   * This method is called by FunctionalTestSetupTrait::rebuildAll() after
-   * enabling the requested modules. It must be called again when additional
-   * modules are enabled later.
-   *
-   * @see \Drupal\Core\Test\FunctionalTestSetupTrait::rebuildAll()
-   * @see \Drupal\Tests\BrowserTestBase::installDrupal()
-   * @see \Drupal\simpletest\WebTestBase::setUp()
-   */
-  protected function resetAll() {
-    // Clear all database and static caches and rebuild data structures.
-    drupal_flush_all_caches();
-    $this->container = \Drupal::getContainer();
-
-    // Reset static variables and reload permissions.
-    $this->refreshVariables();
-  }
-
-  /**
-   * Refreshes in-memory configuration and state information.
-   *
-   * Useful after a page request is made that changes configuration or state in
-   * a different thread.
-   *
-   * In other words calling a settings page with $this->drupalPostForm() with a
-   * changed value would update configuration to reflect that change, but in the
-   * thread that made the call (thread running the test) the changed values
-   * would not be picked up.
-   *
-   * This method clears the cache and loads a fresh copy.
-   */
-  protected function refreshVariables() {
-    // Clear the tag cache.
-    \Drupal::service('cache_tags.invalidator')->resetChecksums();
-    foreach (Cache::getBins() as $backend) {
-      if (is_callable(array($backend, 'reset'))) {
-        $backend->reset();
-      }
-    }
-
-    $this->container->get('config.factory')->reset();
-    $this->container->get('state')->resetCache();
-  }
-
-  /**
-   * Creates a mock request and sets it on the generator.
-   *
-   * This is used to manipulate how the generator generates paths during tests.
-   * It also ensures that calls to $this->drupalGet() will work when running
-   * from run-tests.sh because the url generator no longer looks at the global
-   * variables that are set there but relies on getting this information from a
-   * request object.
-   *
-   * @param bool $clean_urls
-   *   Whether to mock the request using clean urls.
-   * @param array $override_server_vars
-   *   An array of server variables to override.
-   *
-   * @return \Symfony\Component\HttpFoundation\Request
-   *   The mocked request object.
-   */
-  protected function prepareRequestForGenerator($clean_urls = TRUE, $override_server_vars = array()) {
-    $request = Request::createFromGlobals();
-    $server = $request->server->all();
-    if (basename($server['SCRIPT_FILENAME']) != basename($server['SCRIPT_NAME'])) {
-      // We need this for when the test is executed by run-tests.sh.
-      // @todo Remove this once run-tests.sh has been converted to use a Request
-      //   object.
-      $cwd = getcwd();
-      $server['SCRIPT_FILENAME'] = $cwd . '/' . basename($server['SCRIPT_NAME']);
-      $base_path = rtrim($server['REQUEST_URI'], '/');
-    }
-    else {
-      $base_path = $request->getBasePath();
-    }
-    if ($clean_urls) {
-      $request_path = $base_path ? $base_path . '/user' : 'user';
-    }
-    else {
-      $request_path = $base_path ? $base_path . '/index.php/user' : '/index.php/user';
-    }
-    $server = array_merge($server, $override_server_vars);
-
-    $request = Request::create($request_path, 'GET', array(), array(), array(), $server);
-    // Ensure the request time is REQUEST_TIME to ensure that API calls
-    // in the test use the right timestamp.
-    $request->server->set('REQUEST_TIME', REQUEST_TIME);
-    $this->container->get('request_stack')->push($request);
-
-    // The request context is normally set by the router_listener from within
-    // its KernelEvents::REQUEST listener. In the simpletest parent site this
-    // event is not fired, therefore it is necessary to updated the request
-    // context manually here.
-    $this->container->get('router.request_context')->fromRequest($request);
-
-    return $request;
-  }
-
-  /**
-   * Execute the non-interactive installer.
-   *
-   * @see install_drupal()
-   */
-  protected function doInstall() {
-    require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
-    install_drupal($this->classLoader, $this->installParameters());
-  }
-
-  /**
-   * Initialize settings created during install.
-   */
-  protected function initSettings() {
-    Settings::initialize(DRUPAL_ROOT, $this->siteDirectory, $this->classLoader);
-    foreach ($GLOBALS['config_directories'] as $type => $path) {
-      $this->configDirectories[$type] = $path;
-    }
-
-    // After writing settings.php, the installer removes write permissions
-    // from the site directory. To allow drupal_generate_test_ua() to write
-    // a file containing the private key for drupal_valid_test_ua(), the site
-    // directory has to be writable.
-    // TestBase::restoreEnvironment() will delete the entire site directory.
-    // Not using File API; a potential error must trigger a PHP warning.
-    chmod(DRUPAL_ROOT . '/' . $this->siteDirectory, 0777);
-
-    // During tests, cacheable responses should get the debugging cacheability
-    // headers by default.
-    $this->setContainerParameter('http.response.debug_cacheability_headers', TRUE);
-  }
-
-  /**
-   * Initialize various configurations post-installation.
-   *
-   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
-   *   The container.
-   */
-  protected function initConfig(ContainerInterface $container) {
-    $config = $container->get('config.factory');
-
-    // Manually create and configure private and temporary files directories.
-    // While these could be preset/enforced in settings.php like the public
-    // files directory above, some tests expect them to be configurable in the
-    // UI. If declared in settings.php, they would no longer be configurable.
-    file_prepare_directory($this->privateFilesDirectory, FILE_CREATE_DIRECTORY);
-    file_prepare_directory($this->tempFilesDirectory, FILE_CREATE_DIRECTORY);
-    $config->getEditable('system.file')
-      ->set('path.temporary', $this->tempFilesDirectory)
-      ->save();
-
-    // Manually configure the test mail collector implementation to prevent
-    // tests from sending out emails and collect them in state instead.
-    // While this should be enforced via settings.php prior to installation,
-    // some tests expect to be able to test mail system implementations.
-    $config->getEditable('system.mail')
-      ->set('interface.default', 'test_mail_collector')
-      ->save();
-
-    // By default, verbosely display all errors and disable all production
-    // environment optimizations for all tests to avoid needless overhead and
-    // ensure a sane default experience for test authors.
-    // @see https://www.drupal.org/node/2259167
-    $config->getEditable('system.logging')
-      ->set('error_level', 'verbose')
-      ->save();
-    $config->getEditable('system.performance')
-      ->set('css.preprocess', FALSE)
-      ->set('js.preprocess', FALSE)
-      ->save();
-
-    // Set an explicit time zone to not rely on the system one, which may vary
-    // from setup to setup. The Australia/Sydney time zone is chosen so all
-    // tests are run using an edge case scenario (UTC10 and DST). This choice
-    // is made to prevent time zone related regressions and reduce the
-    // fragility of the testing system in general.
-    $config->getEditable('system.date')
-      ->set('timezone.default', 'Australia/Sydney')
-      ->save();
-  }
-
-  /**
-   * Initializes user 1 for the site to be installed.
-   */
-  protected function initUserSession() {
-    $password = $this->randomMachineName();
-    // Define information about the user 1 account.
-    $this->rootUser = new UserSession(array(
-      'uid' => 1,
-      'name' => 'admin',
-      'mail' => 'admin@example.com',
-      'pass_raw' => $password,
-      'passRaw' => $password,
-      'timezone' => date_default_timezone_get(),
-    ));
-
-    // The child site derives its session name from the database prefix when
-    // running web tests.
-    $this->generateSessionName($this->databasePrefix);
-  }
-
-  /**
-   * Initializes the kernel after installation.
-   *
-   * @param \Symfony\Component\HttpFoundation\Request $request
-   *   Request object.
-   *
-   * @return \Symfony\Component\DependencyInjection\ContainerInterface
-   *   The container.
-   */
-  protected function initKernel(Request $request) {
-    $this->kernel = DrupalKernel::createFromRequest($request, $this->classLoader, 'prod', TRUE);
-    $this->kernel->prepareLegacyRequest($request);
-    // Force the container to be built from scratch instead of loaded from the
-    // disk. This forces us to not accidentally load the parent site.
-    return $this->kernel->rebuildContainer();
-  }
-
-  /**
-   * Install modules defined by `static::$modules`.
-   *
-   * To install test modules outside of the testing environment, add
-   * @code
-   * $settings['extension_discovery_scan_tests'] = TRUE;
-   * @endcode
-   * to your settings.php.
-   *
-   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
-   *   The container.
-   */
-  protected function installModulesFromClassProperty(ContainerInterface $container) {
-    $class = get_class($this);
-    $modules = [];
-    while ($class) {
-      if (property_exists($class, 'modules')) {
-        $modules = array_merge($modules, $class::$modules);
-      }
-      $class = get_parent_class($class);
-    }
-    if ($modules) {
-      $modules = array_unique($modules);
-      try {
-        $success = $container->get('module_installer')->install($modules, TRUE);
-        $this->assertTrue($success, SafeMarkup::format('Enabled modules: %modules', ['%modules' => implode(', ', $modules)]));
-      }
-      catch (MissingDependencyException $e) {
-        // The exception message has all the details.
-        $this->fail($e->getMessage());
-      }
-
-      $this->rebuildContainer();
-    }
-  }
-
-  /**
-   * Resets and rebuilds the environment after setup.
-   */
-  protected function rebuildAll() {
-    // Reset/rebuild all data structures after enabling the modules, primarily
-    // to synchronize all data structures and caches between the test runner and
-    // the child site.
-    // @see \Drupal\Core\DrupalKernel::bootCode()
-    // @todo Test-specific setUp() methods may set up further fixtures; find a
-    //   way to execute this after setUp() is done, or to eliminate it entirely.
-    $this->resetAll();
-    $this->kernel->prepareLegacyRequest(\Drupal::request());
-
-    // Explicitly call register() again on the container registered in \Drupal.
-    // @todo This should already be called through
-    //   DrupalKernel::prepareLegacyRequest() -> DrupalKernel::boot() but that
-    //   appears to be calling a different container.
-    $this->container->get('stream_wrapper_manager')->register();
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Test/TestSetupTrait.php b/core/lib/Drupal/Core/Test/TestSetupTrait.php
deleted file mode 100644
index 32e6fca..0000000
--- a/core/lib/Drupal/Core/Test/TestSetupTrait.php
+++ /dev/null
@@ -1,198 +0,0 @@
-<?php
-
-namespace Drupal\Core\Test;
-
-use Drupal\Core\Database\Database;
-
-/**
- * Provides a trait for shared test setup functionality.
- */
-trait TestSetupTrait {
-
-  /**
-   * An array of config object names that are excluded from schema checking.
-   *
-   * @var string[]
-   */
-  protected static $configSchemaCheckerExclusions = array(
-    // Following are used to test lack of or partial schema. Where partial
-    // schema is provided, that is explicitly tested in specific tests.
-    'config_schema_test.noschema',
-    'config_schema_test.someschema',
-    'config_schema_test.schema_data_types',
-    'config_schema_test.no_schema_data_types',
-    // Used to test application of schema to filtering of configuration.
-    'config_test.dynamic.system',
-  );
-
-  /**
-   * The dependency injection container used in the test.
-   *
-   * @var \Symfony\Component\DependencyInjection\ContainerInterface
-   */
-  protected $container;
-
-  /**
-   * The site directory of this test run.
-   *
-   * @var string
-   */
-  protected $siteDirectory = NULL;
-
-  /**
-   * The public file directory for the test environment.
-   *
-   * @see \Drupal\simpletest\TestBase::prepareEnvironment()
-   * @see \Drupal\Tests\BrowserTestBase::prepareEnvironment()
-   *
-   * @var string
-   */
-  protected $publicFilesDirectory;
-
-  /**
-   * The site directory of the original parent site.
-   *
-   * @var string
-   */
-  protected $originalSite;
-
-  /**
-   * The private file directory for the test environment.
-   *
-   * @see \Drupal\simpletest\TestBase::prepareEnvironment()
-   * @see \Drupal\Tests\BrowserTestBase::prepareEnvironment()
-   *
-   * @var string
-   */
-  protected $privateFilesDirectory;
-
-  /**
-   * The original installation profile.
-   *
-   * @var string
-   */
-  protected $originalProfile;
-
-  /**
-   * Set to TRUE to strict check all configuration saved.
-   *
-   * @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
-   *
-   * @var bool
-   */
-  protected $strictConfigSchema = TRUE;
-
-  /**
-   * The DrupalKernel instance used in the test.
-   *
-   * @var \Drupal\Core\DrupalKernel
-   */
-  protected $kernel;
-
-  /**
-   * The temporary file directory for the test environment.
-   *
-   * @see \Drupal\simpletest\TestBase::prepareEnvironment()
-   * @see \Drupal\Tests\BrowserTestBase::prepareEnvironment()
-   *
-   * @var string
-   */
-  protected $tempFilesDirectory;
-
-  /**
-   * The test run ID.
-   *
-   * @var string
-   */
-  protected $testId;
-
-  /**
-   * Returns the database connection to the site running Simpletest.
-   *
-   * @return \Drupal\Core\Database\Connection
-   *   The database connection to use for inserting assertions.
-   */
-  public static function getDatabaseConnection() {
-    return TestDatabase::getConnection();
-  }
-
-  /**
-   * Generates a database prefix for running tests.
-   *
-   * The database prefix is used by prepareEnvironment() to setup a public files
-   * directory for the test to be run, which also contains the PHP error log,
-   * which is written to in case of a fatal error. Since that directory is based
-   * on the database prefix, all tests (even unit tests) need to have one, in
-   * order to access and read the error log.
-   *
-   * The generated database table prefix is used for the Drupal installation
-   * being performed for the test. It is also used as user agent HTTP header
-   * value by the cURL-based browser of WebTestBase, which is sent to the Drupal
-   * installation of the test. During early Drupal bootstrap, the user agent
-   * HTTP header is parsed, and if it matches, all database queries use the
-   * database table prefix that has been generated here.
-   *
-   * @see \Drupal\Tests\BrowserTestBase::prepareEnvironment()
-   * @see \Drupal\simpletest\WebTestBase::curlInitialize()
-   * @see \Drupal\simpletest\TestBase::prepareEnvironment()
-   * @see drupal_valid_test_ua()
-   */
-  private function prepareDatabasePrefix() {
-    $test_db = new TestDatabase();
-    $this->siteDirectory = $test_db->getTestSitePath();
-    $this->databasePrefix = $test_db->getDatabasePrefix();
-  }
-
-  /**
-   * Changes the database connection to the prefixed one.
-   */
-  private function changeDatabasePrefix() {
-    if (empty($this->databasePrefix)) {
-      $this->prepareDatabasePrefix();
-    }
-
-    // If the test is run with argument dburl then use it.
-    $db_url = getenv('SIMPLETEST_DB');
-    if (!empty($db_url)) {
-      $database = Database::convertDbUrlToConnectionInfo($db_url, DRUPAL_ROOT);
-      Database::addConnectionInfo('default', 'default', $database);
-    }
-
-    // Clone the current connection and replace the current prefix.
-    $connection_info = Database::getConnectionInfo('default');
-    if (is_null($connection_info)) {
-      throw new \InvalidArgumentException('There is no database connection so no tests can be run. You must provide a SIMPLETEST_DB environment variable to run PHPUnit based functional tests outside of run-tests.sh.');
-    }
-    else {
-      Database::renameConnection('default', 'simpletest_original_default');
-      foreach ($connection_info as $target => $value) {
-        // Replace the full table prefix definition to ensure that no table
-        // prefixes of the test runner leak into the test.
-        $connection_info[$target]['prefix'] = array(
-          'default' => $value['prefix']['default'] . $this->databasePrefix,
-        );
-      }
-      Database::addConnectionInfo('default', 'default', $connection_info['default']);
-    }
-  }
-
-  /**
-   * Gets the config schema exclusions for this test.
-   *
-   * @return string[]
-   *   An array of config object names that are excluded from schema checking.
-   */
-  protected function getConfigSchemaExclusions() {
-    $class = get_class($this);
-    $exceptions = [];
-    while ($class) {
-      if (property_exists($class, 'configSchemaCheckerExclusions')) {
-        $exceptions = array_merge($exceptions, $class::$configSchemaCheckerExclusions);
-      }
-      $class = get_parent_class($class);
-    }
-    // Filter out any duplicates.
-    return array_unique($exceptions);
-  }
-
-}
diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
index b1c295d..4904150 100644
--- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php
+++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
@@ -158,9 +158,9 @@ public function getPropertyInstance(TypedDataInterface $object, $property_name,
     // Root data type and settings.
     $parts[] = $root_definition->getDataType();
     if ($settings = $root_definition->getSettings()) {
-      // Include the settings serialized as JSON as part of the key. The JSON is
-      // a shorter string than the serialized form, so array access is faster.
-      $parts[] = json_encode($settings);
+      // Hash the settings into a string. crc32 is the fastest way to hash
+      // something for non-cryptographic purposes.
+      $parts[] = hash('crc32b', serialize($settings));
     }
     // Property path for the requested data object. When creating a list item,
     // use 0 in the key as all items look the same.
diff --git a/core/misc/drupal.init.js b/core/misc/drupal.init.js
deleted file mode 100644
index 0e55e19..0000000
--- a/core/misc/drupal.init.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// Allow other JavaScript libraries to use $.
-if (window.jQuery) {
-  jQuery.noConflict();
-}
-
-// Class indicating that JS is enabled; used for styling purpose.
-document.documentElement.className += ' js';
-
-// JavaScript should be made compatible with libraries other than jQuery by
-// wrapping it in an anonymous closure.
-
-(function (domready, Drupal, drupalSettings) {
-
-  'use strict';
-
-  // Attach all behaviors.
-  domready(function () { Drupal.attachBehaviors(document, drupalSettings); });
-
-})(domready, Drupal, window.drupalSettings);
diff --git a/core/misc/drupal.js b/core/misc/drupal.js
index d509795..750d908 100644
--- a/core/misc/drupal.js
+++ b/core/misc/drupal.js
@@ -40,9 +40,17 @@
  */
 window.Drupal = {behaviors: {}, locale: {}};
 
+// Class indicating that JavaScript is enabled; used for styling purpose.
+document.documentElement.className += ' js';
+
+// Allow other JavaScript libraries to use $.
+if (window.jQuery) {
+  jQuery.noConflict();
+}
+
 // JavaScript should be made compatible with libraries other than jQuery by
 // wrapping it in an anonymous closure.
-(function (Drupal, drupalSettings, drupalTranslations) {
+(function (domready, Drupal, drupalSettings, drupalTranslations) {
 
   'use strict';
 
@@ -166,6 +174,9 @@ window.Drupal = {behaviors: {}, locale: {}};
     }
   };
 
+  // Attach all behaviors.
+  domready(function () { Drupal.attachBehaviors(document, drupalSettings); });
+
   /**
    * Detaches registered behaviors from a page element.
    *
@@ -580,4 +591,4 @@ window.Drupal = {behaviors: {}, locale: {}};
     return '<em class="placeholder">' + Drupal.checkPlain(str) + '</em>';
   };
 
-})(Drupal, window.drupalSettings, window.drupalTranslations);
+})(domready, Drupal, window.drupalSettings, window.drupalTranslations);
diff --git a/core/modules/action/action.info.yml b/core/modules/action/action.info.yml
index 0e92946..7559efb 100644
--- a/core/modules/action/action.info.yml
+++ b/core/modules/action/action.info.yml
@@ -1,6 +1,6 @@
 name: Actions
 type: module
-description: 'Allows configuration of tasks for the system to execute in response to events.'
+description: 'Perform tasks on specific events triggered within the system.'
 package: Core
 version: VERSION
 core: 8.x
diff --git a/core/modules/big_pipe/src/Render/BigPipeInterface.php b/core/modules/big_pipe/src/Render/BigPipeInterface.php
index 6d0e5a7..3e2156b 100644
--- a/core/modules/big_pipe/src/Render/BigPipeInterface.php
+++ b/core/modules/big_pipe/src/Render/BigPipeInterface.php
@@ -138,12 +138,6 @@
    *   The HTML response content to send.
    * @param array $attachments
    *   The HTML response's attachments.
-   *
-   * @internal
-   *   This method should only be invoked by
-   *   \Drupal\big_pipe\Render\BigPipeResponse, which is itself an internal
-   *   class. Furthermore, the signature of this method will change in
-   *   https://www.drupal.org/node/2657684.
    */
   public function sendContent($content, array $attachments);
 
diff --git a/core/modules/big_pipe/src/Render/BigPipeResponse.php b/core/modules/big_pipe/src/Render/BigPipeResponse.php
index c6871d5..555e7cb 100644
--- a/core/modules/big_pipe/src/Render/BigPipeResponse.php
+++ b/core/modules/big_pipe/src/Render/BigPipeResponse.php
@@ -13,10 +13,7 @@
  *
  * @see \Drupal\big_pipe\Render\BigPipeInterface
  *
- * @internal
- *   This is a temporary solution until a generic response emitter interface is
- *   created in https://www.drupal.org/node/2577631. Only code internal to
- *   BigPipe should instantiate or type hint to this class.
+ * @todo Will become obsolete with https://www.drupal.org/node/2577631
  */
 class BigPipeResponse extends HtmlResponse {
 
diff --git a/core/modules/big_pipe/src/Render/Placeholder/BigPipeStrategy.php b/core/modules/big_pipe/src/Render/Placeholder/BigPipeStrategy.php
index 8fe3e97..91ad604 100644
--- a/core/modules/big_pipe/src/Render/Placeholder/BigPipeStrategy.php
+++ b/core/modules/big_pipe/src/Render/Placeholder/BigPipeStrategy.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\big_pipe\Render\Placeholder;
 
-use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface;
@@ -261,7 +260,7 @@ protected static function generateBigPipePlaceholderId($original_placeholder, ar
     if (isset($placeholder_render_array['#lazy_builder'])) {
       $callback = $placeholder_render_array['#lazy_builder'][0];
       $arguments = $placeholder_render_array['#lazy_builder'][1];
-      $token = Crypt::hashBase64(serialize($placeholder_render_array));
+      $token = hash('crc32b', serialize($placeholder_render_array));
       return UrlHelper::buildQuery(['callback' => $callback, 'args' => $arguments, 'token' => $token]);
     }
     // When the placeholder's render array is not using a #lazy_builder,
diff --git a/core/modules/big_pipe/src/Tests/BigPipePlaceholderTestCases.php b/core/modules/big_pipe/src/Tests/BigPipePlaceholderTestCases.php
index 9459f75..d2db3cb 100644
--- a/core/modules/big_pipe/src/Tests/BigPipePlaceholderTestCases.php
+++ b/core/modules/big_pipe/src/Tests/BigPipePlaceholderTestCases.php
@@ -51,7 +51,7 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf
     // 1. Real-world example of HTML placeholder.
     $status_messages = new BigPipePlaceholderTestCase(
       ['#type' => 'status_messages'],
-      '<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="a8c34b5e"></drupal-render-placeholder>',
       [
         '#lazy_builder' => [
           'Drupal\Core\Render\Element\StatusMessages::renderMessages',
@@ -59,29 +59,29 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf
         ],
       ]
     );
-    $status_messages->bigPipePlaceholderId = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA';
+    $status_messages->bigPipePlaceholderId = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=a8c34b5e';
     $status_messages->bigPipePlaceholderRenderArray = [
-      '#markup' => '<div data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></div>',
+      '#markup' => '<div data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=a8c34b5e"></div>',
       '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
       '#attached' => [
         'library' => ['big_pipe/big_pipe'],
         'drupalSettings' => [
           'bigPipePlaceholderIds' => [
-            'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA' => TRUE,
+            'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e' => TRUE,
           ],
         ],
         'big_pipe_placeholders' => [
-          'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA' => $status_messages->placeholderRenderArray,
+          'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=a8c34b5e' => $status_messages->placeholderRenderArray,
         ],
       ],
     ];
-    $status_messages->bigPipeNoJsPlaceholder = '<div data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></div>';
+    $status_messages->bigPipeNoJsPlaceholder = '<div data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=a8c34b5e"></div>';
     $status_messages->bigPipeNoJsPlaceholderRenderArray = [
-      '#markup' => '<div data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></div>',
+      '#markup' => '<div data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=a8c34b5e"></div>',
       '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
       '#attached' => [
         'big_pipe_nojs_placeholders' => [
-          '<div data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></div>' => $status_messages->placeholderRenderArray,
+          '<div data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=a8c34b5e"></div>' => $status_messages->placeholderRenderArray,
         ],
       ],
     ];
@@ -109,7 +109,7 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf
         [
           'command' => 'insert',
           'method' => 'replaceWith',
-          'selector' => '[data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"]',
+          'selector' => '[data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e"]',
           'data' => "\n" . '    <div role="contentinfo" aria-label="Status message" class="messages messages--status">' . "\n" . '                  <h2 class="visually-hidden">Status message</h2>' . "\n" . '                    Hello from BigPipe!' . "\n" . '            </div>' . "\n    ",
           'settings' => NULL,
         ],
@@ -244,7 +244,7 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf
         'command' => 'insert',
         'method' => 'replaceWith',
         'selector' => '[data-big-pipe-placeholder-id="timecurrent-timetime"]',
-        'data' => '<time datetime=1991-03-14"></time>',
+        'data' => '<time datetime="1991-03-14"></time>',
         'settings' => NULL,
       ],
     ];
@@ -258,7 +258,7 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf
         ],
       ],
     ];
-    $current_time->embeddedHtmlResponse = '<time datetime=1991-03-14"></time>';
+    $current_time->embeddedHtmlResponse = '<time datetime="1991-03-14"></time>';
 
 
     // 6. Edge case: #lazy_builder that throws an exception.
@@ -267,29 +267,29 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf
         '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']],
         '#create_placeholder' => TRUE,
       ],
-      '<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::exception" arguments="0=llamas&amp;1=suck" token="uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::exception" arguments="0=llamas&amp;1=suck" token="68a75f1a"></drupal-render-placeholder>',
       [
         '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']],
       ]
     );
-    $exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU';
+    $exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=68a75f1a';
     $exception->bigPipePlaceholderRenderArray = [
-      '#markup' => '<div data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU"></div>',
+      '#markup' => '<div data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=68a75f1a"></div>',
       '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
       '#attached' => [
         'library' => ['big_pipe/big_pipe'],
         'drupalSettings' => [
           'bigPipePlaceholderIds' => [
-            'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args[0]=llamas&args[1]=suck&token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU' => TRUE,
+            'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args[0]=llamas&args[1]=suck&token=68a75f1a' => TRUE,
           ],
         ],
         'big_pipe_placeholders' => [
-          'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU' => $exception->placeholderRenderArray,
+          'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=68a75f1a' => $exception->placeholderRenderArray,
         ],
       ],
     ];
     $exception->embeddedAjaxResponseCommands = NULL;
-    $exception->bigPipeNoJsPlaceholder = '<div data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU"></div>';
+    $exception->bigPipeNoJsPlaceholder = '<div data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=68a75f1a"></div>';
     $exception->bigPipeNoJsPlaceholderRenderArray = [
       '#markup' => $exception->bigPipeNoJsPlaceholder,
       '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
@@ -307,29 +307,29 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf
         '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::responseException', []],
         '#create_placeholder' => TRUE,
       ],
-      '<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::responseException" arguments="" token="PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::responseException" arguments="" token="2a9bd022"></drupal-render-placeholder>',
       [
         '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::responseException', []],
       ]
     );
-    $embedded_response_exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU';
+    $embedded_response_exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=2a9bd022';
     $embedded_response_exception->bigPipePlaceholderRenderArray = [
-      '#markup' => '<div data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU"></div>',
+      '#markup' => '<div data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=2a9bd022"></div>',
       '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
       '#attached' => [
         'library' => ['big_pipe/big_pipe'],
         'drupalSettings' => [
           'bigPipePlaceholderIds' => [
-            'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU' => TRUE,
+            'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=2a9bd022' => TRUE,
           ],
         ],
         'big_pipe_placeholders' => [
-          'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU' => $embedded_response_exception->placeholderRenderArray,
+          'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=2a9bd022' => $embedded_response_exception->placeholderRenderArray,
         ],
       ],
     ];
     $embedded_response_exception->embeddedAjaxResponseCommands = NULL;
-    $embedded_response_exception->bigPipeNoJsPlaceholder = '<div data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU"></div>';
+    $embedded_response_exception->bigPipeNoJsPlaceholder = '<div data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&amp;&amp;token=2a9bd022"></div>';
     $embedded_response_exception->bigPipeNoJsPlaceholderRenderArray = [
       '#markup' => $embedded_response_exception->bigPipeNoJsPlaceholder,
       '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
diff --git a/core/modules/big_pipe/src/Tests/BigPipeTest.php b/core/modules/big_pipe/src/Tests/BigPipeTest.php
index bb032e5..a3f773e 100644
--- a/core/modules/big_pipe/src/Tests/BigPipeTest.php
+++ b/core/modules/big_pipe/src/Tests/BigPipeTest.php
@@ -289,7 +289,7 @@ public function testBigPipeMultiOccurrencePlaceholders() {
     // @see performMetaRefresh()
 
     $this->drupalGet(Url::fromRoute('big_pipe_test_multi_occurrence'));
-    $big_pipe_placeholder_id = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA';
+    $big_pipe_placeholder_id = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&amp;args[0]&amp;token=a8c34b5e';
     $expected_placeholder_replacement = '<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="' . $big_pipe_placeholder_id . '">';
     $this->assertRaw('The count is 1.');
     $this->assertNoRaw('The count is 2.');
diff --git a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php
index 30594a5..eeb7b0d 100644
--- a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php
+++ b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipeTestController.php
@@ -85,7 +85,7 @@ public function multiOccurrence() {
    */
   public static function currentTime() {
     return [
-      '#markup' => '<time datetime=' . date('Y-m-d', 668948400) . '"></time>',
+      '#markup' => '<time datetime="' . date('Y-m-d', 668948400) . '"></time>',
       '#cache' => ['max-age' => 0]
     ];
   }
diff --git a/core/modules/block/block.info.yml b/core/modules/block/block.info.yml
index 2a7d743..47501ef 100644
--- a/core/modules/block/block.info.yml
+++ b/core/modules/block/block.info.yml
@@ -1,6 +1,6 @@
 name: Block
 type: module
-description: 'Allows users to configure blocks and to place them in the regions of a theme.'
+description: 'Controls the visual building blocks a page is constructed with. Blocks are boxes of content rendered into an area, or region, of a web page.'
 package: Core
 version: VERSION
 core: 8.x
diff --git a/core/modules/block/src/Tests/BlockFormInBlockTest.php b/core/modules/block/src/Tests/BlockFormInBlockTest.php
index 59b5f5f..20d24b2 100644
--- a/core/modules/block/src/Tests/BlockFormInBlockTest.php
+++ b/core/modules/block/src/Tests/BlockFormInBlockTest.php
@@ -2,10 +2,8 @@
 
 namespace Drupal\block\Tests;
 
-use Drupal\Component\Utility\Crypt;
 use Drupal\simpletest\WebTestBase;
 
-
 /**
  * Tests form in block caching.
  *
@@ -66,7 +64,7 @@ function testCachePerPage() {
   public function testPlaceholders() {
     $this->drupalGet('test-multiple-forms');
 
-    $placeholder = 'form_action_' . Crypt::hashBase64('Drupal\Core\Form\FormBuilder::prepareForm');
+    $placeholder = 'form_action_' . hash('crc32b', 'Drupal\Core\Form\FormBuilder::prepareForm');
     $this->assertText('Form action: ' . $placeholder, 'placeholder found.');
   }
 
diff --git a/core/modules/block_content/block_content.info.yml b/core/modules/block_content/block_content.info.yml
index af286e2..b9ae564 100644
--- a/core/modules/block_content/block_content.info.yml
+++ b/core/modules/block_content/block_content.info.yml
@@ -1,6 +1,6 @@
 name: 'Custom Block'
 type: module
-description: 'Allows users to create custom blocks and block types.'
+description: 'Allows the creation of custom blocks through the user interface.'
 package: Core
 version: VERSION
 core: 8.x
diff --git a/core/modules/block_content/block_content.libraries.yml b/core/modules/block_content/block_content.libraries.yml
index b9549d4..5a1b5b6 100644
--- a/core/modules/block_content/block_content.libraries.yml
+++ b/core/modules/block_content/block_content.libraries.yml
@@ -1,7 +1,8 @@
-# Deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
-# This is just for BC and not used anymore. See https://www.drupal.org/node/2669802
 drupal.block_content:
   version: VERSION
-  js: {}
+  js:
+    js/block_content.js: {}
   dependencies:
-    - core/drupal.entity-form
+    - core/jquery
+    - core/drupal
+    - core/drupal.form
diff --git a/core/misc/entity-form.js b/core/modules/block_content/js/block_content.js
similarity index 82%
rename from core/misc/entity-form.js
rename to core/modules/block_content/js/block_content.js
index f86c416..bbc0533 100644
--- a/core/misc/entity-form.js
+++ b/core/modules/block_content/js/block_content.js
@@ -8,20 +8,20 @@
   'use strict';
 
   /**
-   * Sets summaries about revision and translation of entities.
+   * Sets summaries about revision and translation of block content.
    *
    * @type {Drupal~behavior}
    *
    * @prop {Drupal~behaviorAttach} attach
-   *   Attaches summary behaviour entity form tabs.
+   *   Attaches summary behaviour block content form tabs.
    *
    *   Specifically, it updates summaries to the revision information and the
    *   translation options.
    */
-  Drupal.behaviors.entityContentDetailsSummaries = {
+  Drupal.behaviors.blockContentDetailsSummaries = {
     attach: function (context) {
       var $context = $(context);
-      $context.find('.entity-content-form-revision-information').drupalSetSummary(function (context) {
+      $context.find('.block-content-form-revision-information').drupalSetSummary(function (context) {
         var $revisionContext = $(context);
         var revisionCheckbox = $revisionContext.find('.js-form-item-revision input');
 
@@ -36,7 +36,7 @@
         return Drupal.t('No revision');
       });
 
-      $context.find('details.entity-translation-options').drupalSetSummary(function (context) {
+      $context.find('fieldset.block-content-translation-options').drupalSetSummary(function (context) {
         var $translationContext = $(context);
         var translate;
         var $checkbox = $translationContext.find('.js-form-item-translation-translate input');
diff --git a/core/modules/block_content/src/BlockContentForm.php b/core/modules/block_content/src/BlockContentForm.php
index b3f50f7..8815f56 100644
--- a/core/modules/block_content/src/BlockContentForm.php
+++ b/core/modules/block_content/src/BlockContentForm.php
@@ -4,7 +4,11 @@
 
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Entity\ContentEntityForm;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Form handler for the custom block edit forms.
@@ -12,6 +16,27 @@
 class BlockContentForm extends ContentEntityForm {
 
   /**
+   * The custom block storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $blockContentStorage;
+
+  /**
+   * The custom block type storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $blockContentTypeStorage;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
    * The block content entity.
    *
    * @var \Drupal\block_content\BlockContentInterface
@@ -19,12 +44,62 @@ class BlockContentForm extends ContentEntityForm {
   protected $entity;
 
   /**
+   * Constructs a BlockContentForm object.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\Entity\EntityStorageInterface $block_content_storage
+   *   The custom block storage.
+   * @param \Drupal\Core\Entity\EntityStorageInterface $block_content_type_storage
+   *   The custom block type storage.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   */
+  public function __construct(EntityManagerInterface $entity_manager, EntityStorageInterface $block_content_storage, EntityStorageInterface $block_content_type_storage, LanguageManagerInterface $language_manager) {
+    parent::__construct($entity_manager);
+    $this->blockContentStorage = $block_content_storage;
+    $this->blockContentTypeStorage = $block_content_type_storage;
+    $this->languageManager = $language_manager;
+  }
+
+  /**
    * {@inheritdoc}
    */
-  public function form(array $form, FormStateInterface $form_state) {
+  public static function create(ContainerInterface $container) {
+    $entity_manager = $container->get('entity.manager');
+    return new static(
+      $entity_manager,
+      $entity_manager->getStorage('block_content'),
+      $entity_manager->getStorage('block_content_type'),
+      $container->get('language_manager')
+    );
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityForm::prepareEntity().
+   *
+   * Prepares the custom block object.
+   *
+   * Fills in a few default values, and then invokes
+   * hook_block_content_prepare() on all modules.
+   */
+  protected function prepareEntity() {
     $block = $this->entity;
+    // Set up default values, if required.
+    $block_type = $this->blockContentTypeStorage->load($block->bundle());
+    if (!$block->isNew()) {
+      $block->setRevisionLogMessage(NULL);
+    }
+    // Always use the default revision setting.
+    $block->setNewRevision($block_type->shouldCreateNewRevision());
+  }
 
-    $form = parent::form($form, $form_state);
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, FormStateInterface $form_state) {
+    $block = $this->entity;
+    $account = $this->currentUser();
 
     if ($this->operation == 'edit') {
       $form['#title'] = $this->t('Edit custom block %label', array('%label' => $block->label()));
@@ -34,7 +109,56 @@ public function form(array $form, FormStateInterface $form_state) {
     // names.
     $form['#attributes']['class'][0] = 'block-' . Html::getClass($block->bundle()) . '-form';
 
-    return $form;
+    $form['advanced'] = array(
+      '#type' => 'vertical_tabs',
+      '#weight' => 99,
+    );
+
+    // Add a log field if the "Create new revision" option is checked, or if the
+    // current user has the ability to check that option.
+    $form['revision_information'] = array(
+      '#type' => 'details',
+      '#title' => $this->t('Revision information'),
+      // Open by default when "Create new revision" is checked.
+      '#open' => $block->isNewRevision(),
+      '#group' => 'advanced',
+      '#attributes' => array(
+        'class' => array('block-content-form-revision-information'),
+      ),
+      '#attached' => array(
+        'library' => array('block_content/drupal.block_content'),
+      ),
+      '#weight' => 20,
+      '#access' => $block->isNewRevision() || $account->hasPermission('administer blocks'),
+    );
+
+    $form['revision_information']['revision'] = array(
+      '#type' => 'checkbox',
+      '#title' => $this->t('Create new revision'),
+      '#default_value' => $block->isNewRevision(),
+      '#access' => $account->hasPermission('administer blocks'),
+    );
+
+    // Check the revision log checkbox when the log textarea is filled in.
+    // This must not happen if "Create new revision" is enabled by default,
+    // since the state would auto-disable the checkbox otherwise.
+    if (!$block->isNewRevision()) {
+      $form['revision_information']['revision']['#states'] = array(
+        'checked' => array(
+          'textarea[name="revision_log"]' => array('empty' => FALSE),
+        ),
+      );
+    }
+
+    $form['revision_information']['revision_log'] = array(
+      '#type' => 'textarea',
+      '#title' => $this->t('Revision log message'),
+      '#rows' => 4,
+      '#default_value' => $block->getRevisionLog(),
+      '#description' => $this->t('Briefly describe the changes you have made.'),
+    );
+
+    return parent::form($form, $form_state, $block);
   }
 
   /**
@@ -43,11 +167,19 @@ public function form(array $form, FormStateInterface $form_state) {
   public function save(array $form, FormStateInterface $form_state) {
     $block = $this->entity;
 
+    // Save as a new revision if requested to do so.
+    if (!$form_state->isValueEmpty('revision')) {
+      $block->setNewRevision();
+      // If a new revision is created, save the current user as revision author.
+      $block->setRevisionCreationTime(REQUEST_TIME);
+      $block->setRevisionUserId(\Drupal::currentUser()->id());
+    }
+
     $insert = $block->isNew();
     $block->save();
     $context = array('@type' => $block->bundle(), '%info' => $block->label());
     $logger = $this->logger('block_content');
-    $block_type = $this->getBundleEntity();
+    $block_type = $this->blockContentTypeStorage->load($block->bundle());
     $t_args = array('@type' => $block_type->label(), '%info' => $block->label());
 
     if ($insert) {
diff --git a/core/modules/block_content/src/BlockContentTranslationHandler.php b/core/modules/block_content/src/BlockContentTranslationHandler.php
index b446cde..781ba1b 100644
--- a/core/modules/block_content/src/BlockContentTranslationHandler.php
+++ b/core/modules/block_content/src/BlockContentTranslationHandler.php
@@ -5,6 +5,7 @@
 use Drupal\block_content\Entity\BlockContentType;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\content_translation\ContentTranslationHandler;
+use Drupal\Core\Form\FormStateInterface;
 
 /**
  * Defines the translation handler for custom blocks.
@@ -14,6 +15,23 @@ class BlockContentTranslationHandler extends ContentTranslationHandler {
   /**
    * {@inheritdoc}
    */
+  public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
+    parent::entityFormAlter($form, $form_state, $entity);
+    // Move the translation fieldset to a vertical tab.
+    if (isset($form['translation'])) {
+      $form['translation'] += array(
+        '#group' => 'additional_settings',
+        '#weight' => 100,
+        '#attributes' => array(
+          'class' => array('block-content-translation-options'),
+        ),
+      );
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   protected function entityFormTitle(EntityInterface $entity) {
     $block_type = BlockContentType::load($entity->bundle());
     return t('<em>Edit @type</em> @title', array('@type' => $block_type->label(), '@title' => $entity->label()));
diff --git a/core/modules/block_content/src/BlockContentTypeInterface.php b/core/modules/block_content/src/BlockContentTypeInterface.php
index da3864e..9229dab 100644
--- a/core/modules/block_content/src/BlockContentTypeInterface.php
+++ b/core/modules/block_content/src/BlockContentTypeInterface.php
@@ -3,12 +3,11 @@
 namespace Drupal\block_content;
 
 use Drupal\Core\Config\Entity\ConfigEntityInterface;
-use Drupal\Core\Entity\RevisionableEntityBundleInterface;
 
 /**
  * Provides an interface defining a custom block type entity.
  */
-interface BlockContentTypeInterface extends ConfigEntityInterface, RevisionableEntityBundleInterface {
+interface BlockContentTypeInterface extends ConfigEntityInterface {
 
   /**
    * Returns the description of the block type.
@@ -18,4 +17,12 @@
    */
   public function getDescription();
 
+  /**
+   * Returns whether a new revision should be created by default.
+   *
+   * @return bool
+   *   TRUE if a new revision should be created by default.
+   */
+  public function shouldCreateNewRevision();
+
 }
diff --git a/core/modules/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php
index 51ae6f6..13af342 100644
--- a/core/modules/block_content/src/Entity/BlockContent.php
+++ b/core/modules/block_content/src/Entity/BlockContent.php
@@ -35,7 +35,6 @@
  *   base_table = "block_content",
  *   revision_table = "block_content_revision",
  *   data_table = "block_content_field_data",
- *   show_revision_ui = TRUE,
  *   links = {
  *     "canonical" = "/block/{block_content}",
  *     "delete-form" = "/block/{block_content}/delete",
@@ -183,14 +182,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     $fields['revision_log'] = BaseFieldDefinition::create('string_long')
       ->setLabel(t('Revision log message'))
       ->setDescription(t('The log entry explaining the changes in this revision.'))
-      ->setRevisionable(TRUE)
-      ->setDisplayOptions('form', array(
-        'type' => 'string_textarea',
-        'weight' => 25,
-        'settings' => array(
-          'rows' => 4,
-        ),
-      ));
+      ->setRevisionable(TRUE);
 
     $fields['changed'] = BaseFieldDefinition::create('changed')
       ->setLabel(t('Changed'))
diff --git a/core/modules/book/src/Form/BookOutlineForm.php b/core/modules/book/src/Form/BookOutlineForm.php
index 8b4e364..e304a2b 100644
--- a/core/modules/book/src/Form/BookOutlineForm.php
+++ b/core/modules/book/src/Form/BookOutlineForm.php
@@ -3,10 +3,8 @@
 namespace Drupal\book\Form;
 
 use Drupal\book\BookManagerInterface;
-use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\Entity\ContentEntityForm;
 use Drupal\Core\Entity\EntityManagerInterface;
-use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -37,13 +35,9 @@ class BookOutlineForm extends ContentEntityForm {
    *   The entity manager.
    * @param \Drupal\book\BookManagerInterface $book_manager
    *   The BookManager service.
-   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
-   *   The entity type bundle service.
-   * @param \Drupal\Component\Datetime\TimeInterface $time
-   *   The time service.
    */
-  public function __construct(EntityManagerInterface $entity_manager, BookManagerInterface $book_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
-    parent::__construct($entity_manager, $entity_type_bundle_info, $time);
+  public function __construct(EntityManagerInterface $entity_manager, BookManagerInterface $book_manager) {
+    parent::__construct($entity_manager);
     $this->bookManager = $book_manager;
   }
 
@@ -53,9 +47,7 @@ public function __construct(EntityManagerInterface $entity_manager, BookManagerI
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('entity.manager'),
-      $container->get('book.manager'),
-      $container->get('entity_type.bundle.info'),
-      $container->get('datetime.time')
+      $container->get('book.manager')
     );
   }
 
diff --git a/core/modules/ckeditor/ckeditor.info.yml b/core/modules/ckeditor/ckeditor.info.yml
index 3ad98df..0f91ed4 100644
--- a/core/modules/ckeditor/ckeditor.info.yml
+++ b/core/modules/ckeditor/ckeditor.info.yml
@@ -1,6 +1,6 @@
 name: CKEditor
 type: module
-description: 'Provides a visual text editor (WYSIWYG) and adds a toolbar to text fields using CKEditor.'
+description: "WYSIWYG editing for rich text fields using CKEditor."
 package: Core
 core: 8.x
 version: VERSION
diff --git a/core/modules/ckeditor/js/ckeditor.js b/core/modules/ckeditor/js/ckeditor.js
index b68e897..047211b 100644
--- a/core/modules/ckeditor/js/ckeditor.js
+++ b/core/modules/ckeditor/js/ckeditor.js
@@ -298,20 +298,6 @@
     CKEDITOR.config.autoGrow_maxHeight = 0.7 * (window.innerHeight - displace.offsets.top - displace.offsets.bottom);
   });
 
-  // Redirect on hash change when the original hash has an associated CKEditor.
-  function redirectTextareaFragmentToCKEditorInstance() {
-    var hash = location.hash.substr(1);
-    var element = document.getElementById(hash);
-    if (element) {
-      var editor = CKEDITOR.dom.element.get(element).getEditor();
-      if (editor) {
-        var id = editor.container.getAttribute('id');
-        location.replace('#' + id);
-      }
-    }
-  }
-  $(window).on('hashchange.ckeditor', redirectTextareaFragmentToCKEditorInstance);
-
   // Set autoGrow to make the editor grow the moment it is created.
   CKEDITOR.config.autoGrow_onStartup = true;
 
diff --git a/core/modules/ckeditor/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php b/core/modules/ckeditor/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
deleted file mode 100644
index 80de46d..0000000
--- a/core/modules/ckeditor/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
+++ /dev/null
@@ -1,121 +0,0 @@
-<?php
-
-namespace Drupal\Tests\ckeditor\FunctionalJavascript;
-
-use Drupal\Core\Entity\Entity\EntityFormDisplay;
-use Drupal\editor\Entity\Editor;
-use Drupal\field\Entity\FieldConfig;
-use Drupal\field\Entity\FieldStorageConfig;
-use Drupal\filter\Entity\FilterFormat;
-use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
-use Drupal\node\Entity\NodeType;
-
-/**
- * Tests the integration of CKEditor.
- *
- * @group ckeditor
- */
-class CKEditorIntegrationTest extends JavascriptTestBase {
-
-  /**
-   * The account.
-   *
-   * @var \Drupal\user\UserInterface
-   */
-  protected $account;
-
-  /**
-   * {@inheritdoc}
-   */
-  public static $modules = ['node', 'ckeditor', 'filter'];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-
-    // Create a text format and associate CKEditor.
-    $filtered_html_format = FilterFormat::create([
-      'format' => 'filtered_html',
-      'name' => 'Filtered HTML',
-      'weight' => 0,
-    ]);
-    $filtered_html_format->save();
-
-    Editor::create([
-      'format' => 'filtered_html',
-      'editor' => 'ckeditor',
-    ])->save();
-
-    // Create a node type for testing.
-    NodeType::create(['type' => 'page', 'name' => 'page'])->save();
-
-    $field_storage = FieldStorageConfig::loadByName('node', 'body');
-
-    // Create a body field instance for the 'page' node type.
-    FieldConfig::create([
-      'field_storage' => $field_storage,
-      'bundle' => 'page',
-      'label' => 'Body',
-      'settings' => ['display_summary' => TRUE],
-      'required' => TRUE,
-    ])->save();
-
-    // Assign widget settings for the 'default' form mode.
-    EntityFormDisplay::create([
-      'targetEntityType' => 'node',
-      'bundle' => 'page',
-      'mode' => 'default',
-      'status' => TRUE,
-    ])->setComponent('body', ['type' => 'text_textarea_with_summary'])
-      ->save();
-
-    $this->account = $this->drupalCreateUser([
-      'administer nodes',
-      'create page content',
-      'use text format filtered_html',
-    ]);
-    $this->drupalLogin($this->account);
-  }
-
-  /**
-   * Tests if the fragment link to a textarea works with CKEditor enabled.
-   */
-  public function testFragmentLink() {
-    $session = $this->getSession();
-    $web_assert = $this->assertSession();
-    $ckeditor_id = '#cke_edit-body-0-value';
-
-    $this->drupalGet('node/add/page');
-
-    $session->getPage();
-
-    // Add a bottom margin to the title field to be sure the body field is not
-    // visible. PhantomJS runs with a resolution of 1024x768px.
-    $session->executeScript("document.getElementById('edit-title-0-value').style.marginBottom = '800px';");
-
-    // Check that the CKEditor-enabled body field is currently not visible in
-    // the viewport.
-    $web_assert->assertNotVisibleInViewport('css', $ckeditor_id, 'topLeft', 'CKEditor-enabled body field is visible.');
-
-    $before_url = $session->getCurrentUrl();
-
-    // Trigger a hash change with as target the hidden textarea.
-    $session->executeScript("location.hash = '#edit-body-0-value';");
-
-    // Check that the CKEditor-enabled body field is visible in the viewport.
-    $web_assert->assertVisibleInViewport('css', $ckeditor_id, 'topLeft', 'CKEditor-enabled body field is not visible.');
-
-    // Use JavaScript to go back in the history instead of
-    // \Behat\Mink\Session::back() because that function doesn't work after a
-    // hash change.
-    $session->executeScript("history.back();");
-
-    $after_url = $session->getCurrentUrl();
-
-    // Check that going back in the history worked.
-    self::assertEquals($before_url, $after_url, 'History back works.');
-  }
-
-}
diff --git a/core/modules/comment/src/CommentForm.php b/core/modules/comment/src/CommentForm.php
index d162a95..75191a3 100644
--- a/core/modules/comment/src/CommentForm.php
+++ b/core/modules/comment/src/CommentForm.php
@@ -3,14 +3,12 @@
 namespace Drupal\comment;
 
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
-use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Entity\ContentEntityForm;
 use Drupal\Core\Entity\EntityConstraintViolationListInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
-use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -42,9 +40,7 @@ public static function create(ContainerInterface $container) {
     return new static(
       $container->get('entity.manager'),
       $container->get('current_user'),
-      $container->get('renderer'),
-      $container->get('entity_type.bundle.info'),
-      $container->get('datetime.time')
+      $container->get('renderer')
     );
   }
 
@@ -57,13 +53,9 @@ public static function create(ContainerInterface $container) {
    *   The current user.
    * @param \Drupal\Core\Render\RendererInterface $renderer
    *   The renderer.
-   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
-   *   The entity type bundle service.
-   * @param \Drupal\Component\Datetime\TimeInterface $time
-   *   The time service.
    */
-  public function __construct(EntityManagerInterface $entity_manager, AccountInterface $current_user, RendererInterface $renderer, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
-    parent::__construct($entity_manager, $entity_type_bundle_info, $time);
+  public function __construct(EntityManagerInterface $entity_manager, AccountInterface $current_user, RendererInterface $renderer) {
+    parent::__construct($entity_manager);
     $this->currentUser = $current_user;
     $this->renderer = $renderer;
   }
diff --git a/core/modules/contact/src/MessageForm.php b/core/modules/contact/src/MessageForm.php
index 32fb4d5..668dd06 100644
--- a/core/modules/contact/src/MessageForm.php
+++ b/core/modules/contact/src/MessageForm.php
@@ -2,11 +2,9 @@
 
 namespace Drupal\contact;
 
-use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\Datetime\DateFormatterInterface;
 use Drupal\Core\Entity\ContentEntityForm;
 use Drupal\Core\Entity\EntityManagerInterface;
-use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Flood\FloodInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
@@ -65,13 +63,9 @@ class MessageForm extends ContentEntityForm {
    *   The contact mail handler service.
    * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
    *   The date service.
-   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
-   *   The entity type bundle service.
-   * @param \Drupal\Component\Datetime\TimeInterface $time
-   *   The time service.
    */
-  public function __construct(EntityManagerInterface $entity_manager, FloodInterface $flood, LanguageManagerInterface $language_manager, MailHandlerInterface $mail_handler, DateFormatterInterface $date_formatter, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
-    parent::__construct($entity_manager, $entity_type_bundle_info, $time);
+  public function __construct(EntityManagerInterface $entity_manager, FloodInterface $flood, LanguageManagerInterface $language_manager, MailHandlerInterface $mail_handler, DateFormatterInterface $date_formatter) {
+    parent::__construct($entity_manager);
     $this->flood = $flood;
     $this->languageManager = $language_manager;
     $this->mailHandler = $mail_handler;
@@ -87,9 +81,7 @@ public static function create(ContainerInterface $container) {
       $container->get('flood'),
       $container->get('language_manager'),
       $container->get('contact.mail_handler'),
-      $container->get('date.formatter'),
-      $container->get('entity_type.bundle.info'),
-      $container->get('datetime.time')
+      $container->get('date.formatter')
     );
   }
 
diff --git a/core/modules/content_moderation/config/install/content_moderation.state.archived.yml b/core/modules/content_moderation/config/install/content_moderation.state.archived.yml
new file mode 100644
index 0000000..0279481
--- /dev/null
+++ b/core/modules/content_moderation/config/install/content_moderation.state.archived.yml
@@ -0,0 +1,8 @@
+langcode: en
+status: true
+dependencies: {  }
+id: archived
+label: Archived
+published: false
+default_revision: true
+weight: -8
diff --git a/core/modules/content_moderation/config/install/content_moderation.state.draft.yml b/core/modules/content_moderation/config/install/content_moderation.state.draft.yml
new file mode 100644
index 0000000..c7eb64c
--- /dev/null
+++ b/core/modules/content_moderation/config/install/content_moderation.state.draft.yml
@@ -0,0 +1,8 @@
+langcode: en
+status: true
+dependencies: {  }
+id: draft
+label: Draft
+published: false
+default_revision: false
+weight: -10
diff --git a/core/modules/content_moderation/config/install/content_moderation.state.published.yml b/core/modules/content_moderation/config/install/content_moderation.state.published.yml
new file mode 100644
index 0000000..8467e86
--- /dev/null
+++ b/core/modules/content_moderation/config/install/content_moderation.state.published.yml
@@ -0,0 +1,8 @@
+langcode: en
+status: true
+dependencies: {  }
+id: published
+label: Published
+published: true
+default_revision: true
+weight: -9
diff --git a/core/modules/content_moderation/config/install/content_moderation.state_transition.archived_draft.yml b/core/modules/content_moderation/config/install/content_moderation.state_transition.archived_draft.yml
new file mode 100644
index 0000000..8fbf9c3
--- /dev/null
+++ b/core/modules/content_moderation/config/install/content_moderation.state_transition.archived_draft.yml
@@ -0,0 +1,11 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - content_moderation.state.archived
+    - content_moderation.state.draft
+id: archived_draft
+label: 'Un-archive to Draft'
+stateFrom: archived
+stateTo: draft
+weight: -5
diff --git a/core/modules/content_moderation/config/install/content_moderation.state_transition.archived_published.yml b/core/modules/content_moderation/config/install/content_moderation.state_transition.archived_published.yml
new file mode 100644
index 0000000..4be7600
--- /dev/null
+++ b/core/modules/content_moderation/config/install/content_moderation.state_transition.archived_published.yml
@@ -0,0 +1,11 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - content_moderation.state.archived
+    - content_moderation.state.published
+id: archived_published
+label: 'Un-archive'
+stateFrom: archived
+stateTo: published
+weight: -4
diff --git a/core/modules/content_moderation/config/install/content_moderation.state_transition.draft_draft.yml b/core/modules/content_moderation/config/install/content_moderation.state_transition.draft_draft.yml
new file mode 100644
index 0000000..0ba0f34
--- /dev/null
+++ b/core/modules/content_moderation/config/install/content_moderation.state_transition.draft_draft.yml
@@ -0,0 +1,10 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - content_moderation.state.draft
+id: draft_draft
+label: 'Create New Draft'
+stateFrom: draft
+stateTo: draft
+weight: -10
diff --git a/core/modules/content_moderation/config/install/content_moderation.state_transition.draft_published.yml b/core/modules/content_moderation/config/install/content_moderation.state_transition.draft_published.yml
new file mode 100644
index 0000000..cf95d3d
--- /dev/null
+++ b/core/modules/content_moderation/config/install/content_moderation.state_transition.draft_published.yml
@@ -0,0 +1,11 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - content_moderation.state.draft
+    - content_moderation.state.published
+id: draft_published
+label: 'Publish'
+stateFrom: draft
+stateTo: published
+weight: -9
diff --git a/core/modules/content_moderation/config/install/content_moderation.state_transition.published_archived.yml b/core/modules/content_moderation/config/install/content_moderation.state_transition.published_archived.yml
new file mode 100644
index 0000000..f3a866a
--- /dev/null
+++ b/core/modules/content_moderation/config/install/content_moderation.state_transition.published_archived.yml
@@ -0,0 +1,11 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - content_moderation.state.archived
+    - content_moderation.state.published
+id: published_archived
+label: 'Archive'
+stateFrom: published
+stateTo: archived
+weight: -6
diff --git a/core/modules/content_moderation/config/install/content_moderation.state_transition.published_draft.yml b/core/modules/content_moderation/config/install/content_moderation.state_transition.published_draft.yml
new file mode 100644
index 0000000..bd25a31
--- /dev/null
+++ b/core/modules/content_moderation/config/install/content_moderation.state_transition.published_draft.yml
@@ -0,0 +1,11 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - content_moderation.state.draft
+    - content_moderation.state.published
+id: published_draft
+label: 'Create New Draft'
+stateFrom: published
+stateTo: draft
+weight: -8
diff --git a/core/modules/content_moderation/config/install/content_moderation.state_transition.published_published.yml b/core/modules/content_moderation/config/install/content_moderation.state_transition.published_published.yml
new file mode 100644
index 0000000..3c09a85
--- /dev/null
+++ b/core/modules/content_moderation/config/install/content_moderation.state_transition.published_published.yml
@@ -0,0 +1,10 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - content_moderation.state.published
+id: published_published
+label: 'Publish'
+stateFrom: published
+stateTo: published
+weight: -7
diff --git a/core/modules/content_moderation/config/install/workflows.workflow.editorial.yml b/core/modules/content_moderation/config/install/workflows.workflow.editorial.yml
deleted file mode 100644
index d0243b1..0000000
--- a/core/modules/content_moderation/config/install/workflows.workflow.editorial.yml
+++ /dev/null
@@ -1,63 +0,0 @@
-langcode: en
-status: true
-dependencies:
-  module:
-    - content_moderation
-id: editorial
-label: 'Editorial workflow'
-states:
-  archived:
-    label: Archived
-    weight: 5
-  draft:
-    label: Draft
-    weight: -5
-  published:
-    label: Published
-    weight: 0
-transitions:
-  archive:
-    label: Archive
-    from:
-      - published
-    to: archived
-    weight: 2
-  archived_draft:
-    label: 'Un-archive to Draft'
-    from:
-      - archived
-    to: draft
-    weight: 3
-  archived_published:
-    label: Un-archive
-    from:
-      - archived
-    to: published
-    weight: 4
-  create_new_draft:
-    label: 'Create New Draft'
-    from:
-      - draft
-      - published
-    to: draft
-    weight: 0
-  publish:
-    label: Publish
-    from:
-      - draft
-      - published
-    to: published
-    weight: 1
-type: content_moderation
-type_settings:
-  states:
-    archived:
-      published: false
-      default_revision: true
-    draft:
-      published: false
-      default_revision: false
-    published:
-      published: true
-      default_revision: true
-  entity_types: {  }
diff --git a/core/modules/content_moderation/config/schema/content_moderation.schema.yml b/core/modules/content_moderation/config/schema/content_moderation.schema.yml
index b791b67..7f9e8fd 100644
--- a/core/modules/content_moderation/config/schema/content_moderation.schema.yml
+++ b/core/modules/content_moderation/config/schema/content_moderation.schema.yml
@@ -1,33 +1,79 @@
-views.filter.latest_revision:
-  type: views_filter
-  label: 'Latest revision'
+content_moderation.state.*:
+  type: config_entity
+  label: 'Moderation state config'
   mapping:
-    value:
+    id:
       type: string
-      label: 'Value'
+      label: 'ID'
+    label:
+      type: label
+      label: 'Label'
+    published:
+      type: boolean
+      label: 'Is published'
+    default_revision:
+      type: boolean
+      label: 'Is default revision'
+    weight:
+      type: integer
+      label: 'Weight'
+
+content_moderation.state_transition.*:
+  type: config_entity
+  label: 'Moderation state transition config'
+  mapping:
+    id:
+      type: string
+      label: 'ID'
+    label:
+      type: label
+      label: 'Label'
+    stateFrom:
+      type: string
+      label: 'From state'
+    stateTo:
+      type: string
+      label: 'To state'
+    weight:
+      type: integer
+      label: 'Weight'
 
-workflow.type_settings.content_moderation:
+node.type.*.third_party.content_moderation:
   type: mapping
+  label: 'Enable moderation states for this node type'
   mapping:
-    states:
+    enabled:
+      type: boolean
+      label: 'Moderation states enabled'
+    allowed_moderation_states:
       type: sequence
-      label: 'Additional state configuration for content moderation'
       sequence:
-        type: mapping
-        label: 'States'
-        mapping:
-          published:
-            type: boolean
-            label: 'Is published'
-          default_revision:
-            type: boolean
-            label: 'Is default revision'
-    entity_types:
+        type: string
+        label: 'Moderation state'
+    default_moderation_state:
+      type: string
+      label: 'Moderation state for new content'
+
+block_content.type.*.third_party.content_moderation:
+  type: mapping
+  label: 'Enable moderation states for this block content type'
+  mapping:
+    enabled:
+      type: boolean
+      label: 'Moderation states enabled'
+    allowed_moderation_states:
       type: sequence
-      label: 'Entity types'
       sequence:
-        type: sequence
-        label: 'Bundles'
-        sequence:
-          type: string
-          label: 'Bundle ID'
+        type: string
+        label: 'Moderation state'
+    default_moderation_state:
+      type: string
+      label: 'Moderation state for new block content'
+
+views.filter.latest_revision:
+  type: views_filter
+  label: 'Latest revision'
+  mapping:
+    value:
+      type: string
+      label: 'Value'
diff --git a/core/modules/content_moderation/content_moderation.info.yml b/core/modules/content_moderation/content_moderation.info.yml
index ca53b59..6d92b64 100644
--- a/core/modules/content_moderation/content_moderation.info.yml
+++ b/core/modules/content_moderation/content_moderation.info.yml
@@ -5,5 +5,3 @@ version: VERSION
 core: 8.x
 package: Core (Experimental)
 configure: content_moderation.overview
-dependencies:
-  - workflows
diff --git a/core/modules/content_moderation/content_moderation.links.action.yml b/core/modules/content_moderation/content_moderation.links.action.yml
new file mode 100644
index 0000000..cbb2d3f
--- /dev/null
+++ b/core/modules/content_moderation/content_moderation.links.action.yml
@@ -0,0 +1,11 @@
+entity.moderation_state.add_form:
+  route_name: 'entity.moderation_state.add_form'
+  title: 'Add moderation state'
+  appears_on:
+    - entity.moderation_state.collection
+
+entity.moderation_state_transition.add_form:
+  route_name: 'entity.moderation_state_transition.add_form'
+  title: 'Add moderation state transition'
+  appears_on:
+    - entity.moderation_state_transition.collection
diff --git a/core/modules/content_moderation/content_moderation.links.menu.yml b/core/modules/content_moderation/content_moderation.links.menu.yml
new file mode 100644
index 0000000..0fcb3eb
--- /dev/null
+++ b/core/modules/content_moderation/content_moderation.links.menu.yml
@@ -0,0 +1,21 @@
+# Moderation state menu items definition
+content_moderation.overview:
+  title: 'Content moderation'
+  route_name: content_moderation.overview
+  description: 'Configure states and transitions for entities.'
+  parent: system.admin_config_workflow
+
+entity.moderation_state.collection:
+  title: 'Moderation states'
+  route_name: entity.moderation_state.collection
+  description: 'Administer moderation states.'
+  parent: content_moderation.overview
+  weight: 10
+
+# Moderation state transition menu items definition
+entity.moderation_state_transition.collection:
+  title: 'Moderation state transitions'
+  route_name: entity.moderation_state_transition.collection
+  description: 'Administer moderation states transitions.'
+  parent: content_moderation.overview
+  weight: 20
diff --git a/core/modules/content_moderation/content_moderation.links.task.yml b/core/modules/content_moderation/content_moderation.links.task.yml
index f92e92e..d715219 100644
--- a/core/modules/content_moderation/content_moderation.links.task.yml
+++ b/core/modules/content_moderation/content_moderation.links.task.yml
@@ -1,3 +1,3 @@
-content_moderation.workflows:
+moderation_state.entities:
   deriver: 'Drupal\content_moderation\Plugin\Derivative\DynamicLocalTasks'
   weight: 100
diff --git a/core/modules/content_moderation/content_moderation.module b/core/modules/content_moderation/content_moderation.module
index 07ef7c0..582242b 100644
--- a/core/modules/content_moderation/content_moderation.module
+++ b/core/modules/content_moderation/content_moderation.module
@@ -18,11 +18,9 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Session\AccountInterface;
-use Drupal\workflows\WorkflowInterface;
 use Drupal\node\NodeInterface;
 use Drupal\node\Plugin\Action\PublishNode;
 use Drupal\node\Plugin\Action\UnpublishNode;
-use Drupal\workflows\Entity\Workflow;
 
 /**
  * Implements hook_help().
@@ -33,13 +31,15 @@ function content_moderation_help($route_name, RouteMatchInterface $route_match)
     case 'help.page.content_moderation':
       $output = '';
       $output .= '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('The Content Moderation module provides moderation for content by applying workflows to content. For more information, see the <a href=":content_moderation">online documentation for the Content Moderation module</a>.', [':content_moderation' => 'https://www.drupal.org/documentation/modules/content_moderation']) . '</p>';
+      $output .= '<p>' . t('The Content Moderation module provides basic moderation for content. This lets site admins define states for content, and then define transitions between those states. For more information, see the <a href=":content_moderation">online documentation for the Content Moderation module</a>.', [':content_moderation' => 'https://www.drupal.org/documentation/modules/content_moderation']) . '</p>';
       $output .= '<h3>' . t('Uses') . '</h3>';
       $output .= '<dl>';
-      $output .= '<dt>' . t('Configuring workflows') . '</dt>';
-      $output .= '<dd>' . t('Enable the Workflow UI module to create, edit and delete content moderation workflows.') . '</p>';
+      $output .= '<dt>' . t('Moderation states') . '</dt>';
+      $output .= '<dd>' . t('Moderation states provide the <em>Draft</em> and <em>Archived</em> states as additions to the basic <em>Published</em> option. You can click the blue <em>Add Moderation state</em> button and create new states.') . '</dd>';
+      $output .= '<dt>' . t('Moderation state transitions') . '</dt>';
+      $output .= '<dd>' . t('Using the "Moderation state transitions" screen, you can create the actual workflow. You decide the direction in which content moves from state to state, and which user roles are allowed to make that move.') . '</dd>';
       $output .= '<dt>' . t('Configure Content Moderation permissions') . '</dt>';
-      $output .= '<dd>' . t('Each transition is exposed as a permission. If a user has the permission for a transition, then they can move that node from the start state to the end state') . '</p>';
+      $output .= '<dd>' . t('Each state is exposed as a permission. If a user has the permission for a transition, then they can move that node from the start state to the end state') . '</p>';
       $output .= '</dl>';
       return $output;
   }
@@ -182,17 +182,15 @@ function content_moderation_node_access(NodeInterface $node, $operation, Account
 
     $access_result->addCacheableDependency($node);
   }
-  elseif ($operation === 'update' && $moderation_info->isModeratedEntity($node) && $node->moderation_state) {
+  elseif ($operation === 'update' && $moderation_info->isModeratedEntity($node) && $node->moderation_state && $node->moderation_state->target_id) {
     /** @var \Drupal\content_moderation\StateTransitionValidation $transition_validation */
     $transition_validation = \Drupal::service('content_moderation.state_transition_validation');
 
-    $valid_transition_targets = $transition_validation->getValidTransitions($node, $account);
+    $valid_transition_targets = $transition_validation->getValidTransitionTargets($node, $account);
     $access_result = $valid_transition_targets ? AccessResult::neutral() : AccessResult::forbidden();
 
     $access_result->addCacheableDependency($node);
     $access_result->addCacheableDependency($account);
-    $workflow = \Drupal::service('content_moderation.moderation_information')->getWorkflowForEntity($node);
-    $access_result->addCacheableDependency($workflow);
     foreach ($valid_transition_targets as $valid_transition_target) {
       $access_result->addCacheableDependency($valid_transition_target);
     }
@@ -224,39 +222,3 @@ function content_moderation_action_info_alter(&$definitions) {
     $definitions['node_unpublish_action']['class'] = ModerationOptOutUnpublishNode::class;
   }
 }
-
-/**
- * Implements hook_entity_bundle_info_alter().
- */
-function content_moderation_entity_bundle_info_alter(&$bundles) {
-  /** @var \Drupal\workflows\WorkflowInterface $workflow */
-  foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
-    /** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */
-    $plugin = $workflow->getTypePlugin();
-    foreach ($plugin->getEntityTypes() as $entity_type_id) {
-      foreach ($plugin->getBundlesForEntityType($entity_type_id) as $bundle_id) {
-        if (isset($bundles[$entity_type_id][$bundle_id])) {
-          $bundles[$entity_type_id][$bundle_id]['workflow'] = $workflow->id();
-        }
-      }
-    }
-  }
-}
-
-/**
- * Implements hook_ENTITY_TYPE_insert().
- */
-function content_moderation_workflow_insert(WorkflowInterface $entity) {
-  // Clear bundle cache so workflow gets added or removed from the bundle
-  // information.
-  \Drupal::service('entity_type.bundle.info')->clearCachedBundles();
-  // Clear field cache so extra field is added or removed.
-  \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
-}
-
-/**
- * Implements hook_ENTITY_TYPE_update().
- */
-function content_moderation_workflow_update(WorkflowInterface $entity) {
-  content_moderation_workflow_insert($entity);
-}
diff --git a/core/modules/content_moderation/content_moderation.permissions.yml b/core/modules/content_moderation/content_moderation.permissions.yml
index af28bbf..293a77d 100644
--- a/core/modules/content_moderation/content_moderation.permissions.yml
+++ b/core/modules/content_moderation/content_moderation.permissions.yml
@@ -2,13 +2,18 @@ view any unpublished content:
   title: 'View any unpublished content'
   description: 'This permission is necessary for any users that may moderate content.'
 
-'view content moderation':
-  title: 'View content moderation'
-  description: 'View content moderation.'
+'view moderation states':
+  title: 'View moderation states'
+  description: 'View moderation states.'
 
-'administer content moderation':
-  title: 'Administer content moderation'
-  description: 'Administer workflows on content entities.'
+'administer moderation states':
+  title: 'Administer moderation states'
+  description: 'Create and edit moderation states.'
+  'restrict access': TRUE
+
+'administer moderation state transitions':
+  title: 'Administer content moderation state transitions'
+  description: 'Create and edit content moderation state transitions.'
   'restrict access': TRUE
 
 view latest version:
diff --git a/core/modules/content_moderation/content_moderation.routing.yml b/core/modules/content_moderation/content_moderation.routing.yml
new file mode 100644
index 0000000..912eed8
--- /dev/null
+++ b/core/modules/content_moderation/content_moderation.routing.yml
@@ -0,0 +1,7 @@
+content_moderation.overview:
+  path: '/admin/config/workflow/moderation'
+  defaults:
+    _controller: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+    _title: 'Content moderation'
+  requirements:
+    _permission: 'access administration pages'
diff --git a/core/modules/content_moderation/content_moderation.services.yml b/core/modules/content_moderation/content_moderation.services.yml
index 904bc0d..75f0c64 100644
--- a/core/modules/content_moderation/content_moderation.services.yml
+++ b/core/modules/content_moderation/content_moderation.services.yml
@@ -6,10 +6,10 @@ services:
       - { name: paramconverter, priority: 5 }
   content_moderation.state_transition_validation:
     class: \Drupal\content_moderation\StateTransitionValidation
-    arguments: ['@content_moderation.moderation_information']
+    arguments: ['@entity_type.manager', '@entity.query']
   content_moderation.moderation_information:
     class: Drupal\content_moderation\ModerationInformation
-    arguments: ['@entity_type.manager', '@entity_type.bundle.info']
+    arguments: ['@entity_type.manager']
   access_check.latest_revision:
     class: Drupal\content_moderation\Access\LatestRevisionCheck
     arguments: ['@content_moderation.moderation_information']
diff --git a/core/modules/content_moderation/src/ContentModerationState.php b/core/modules/content_moderation/src/ContentModerationState.php
deleted file mode 100644
index 34262eb..0000000
--- a/core/modules/content_moderation/src/ContentModerationState.php
+++ /dev/null
@@ -1,114 +0,0 @@
-<?php
-
-namespace Drupal\content_moderation;
-
-use Drupal\workflows\StateInterface;
-
-/**
- * A value object representing a workflow state for content moderation.
- */
-class ContentModerationState implements StateInterface {
-
-  /**
-   * The vanilla state object from the Workflow module.
-   *
-   * @var \Drupal\workflows\StateInterface
-   */
-  protected $state;
-
-  /**
-   * If entities should be published if in this state.
-   *
-   * @var bool
-   */
-  protected $published;
-
-  /**
-   * If entities should be the default revision if in this state.
-   *
-   * @var bool
-   */
-  protected $defaultRevision;
-
-  /**
-   * ContentModerationState constructor.
-   *
-   * Decorates state objects to add methods to determine if an entity should be
-   * published or made the default revision.
-   *
-   * @param \Drupal\workflows\StateInterface $state
-   *   The vanilla state object from the Workflow module.
-   * @param bool $published
-   *   (optional) TRUE if entities should be published if in this state, FALSE
-   *   if not. Defaults to FALSE.
-   * @param bool $default_revision
-   *   (optional) TRUE if entities should be the default revision if in this
-   *   state, FALSE if not. Defaults to FALSE.
-   */
-  public function __construct(StateInterface $state, $published = FALSE, $default_revision = FALSE) {
-    $this->state = $state;
-    $this->published = $published;
-    $this->defaultRevision = $default_revision;
-  }
-
-  /**
-   * Determines if entities should be published if in this state.
-   *
-   * @return bool
-   */
-  public function isPublishedState() {
-    return $this->published;
-  }
-
-  /**
-   * Determines if entities should be the default revision if in this state.
-   *
-   * @return bool
-   */
-  public function isDefaultRevisionState() {
-    return $this->defaultRevision;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function id() {
-    return $this->state->id();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function label() {
-    return $this->state->label();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function weight() {
-    return $this->state->weight();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function canTransitionTo($to_state_id) {
-    return $this->state->canTransitionTo($to_state_id);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getTransitionTo($to_state_id) {
-    return $this->state->getTransitionTo($to_state_id);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getTransitions() {
-    return $this->state->getTransitions();
-  }
-
-}
diff --git a/core/modules/content_moderation/src/Entity/ContentModerationState.php b/core/modules/content_moderation/src/Entity/ContentModerationState.php
index d60dad7..978408f 100644
--- a/core/modules/content_moderation/src/Entity/ContentModerationState.php
+++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php
@@ -55,17 +55,10 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ->setTranslatable(TRUE)
       ->setRevisionable(TRUE);
 
-    $fields['workflow'] = BaseFieldDefinition::create('entity_reference')
-      ->setLabel(t('Workflow'))
-      ->setDescription(t('The workflow the moderation state is in.'))
-      ->setSetting('target_type', 'workflow')
-      ->setRequired(TRUE)
-      ->setTranslatable(TRUE)
-      ->setRevisionable(TRUE);
-
-    $fields['moderation_state'] = BaseFieldDefinition::create('string')
+    $fields['moderation_state'] = BaseFieldDefinition::create('entity_reference')
       ->setLabel(t('Moderation state'))
       ->setDescription(t('The moderation state of the referenced content.'))
+      ->setSetting('target_type', 'moderation_state')
       ->setRequired(TRUE)
       ->setTranslatable(TRUE)
       ->setRevisionable(TRUE)
@@ -162,7 +155,7 @@ public function save() {
     if ($related_entity instanceof TranslatableInterface) {
       $related_entity = $related_entity->getTranslation($this->activeLangcode);
     }
-    $related_entity->moderation_state = $this->moderation_state;
+    $related_entity->moderation_state->target_id = $this->moderation_state->target_id;
     return $related_entity->save();
   }
 
diff --git a/core/modules/content_moderation/src/Entity/Handler/NodeModerationHandler.php b/core/modules/content_moderation/src/Entity/Handler/NodeModerationHandler.php
index 247d352..83de187 100644
--- a/core/modules/content_moderation/src/Entity/Handler/NodeModerationHandler.php
+++ b/core/modules/content_moderation/src/Entity/Handler/NodeModerationHandler.php
@@ -2,11 +2,8 @@
 
 namespace Drupal\content_moderation\Entity\Handler;
 
-use Drupal\content_moderation\ModerationInformationInterface;
 use Drupal\Core\Entity\ContentEntityInterface;
-use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Form\FormStateInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Customizations for node entities.
@@ -14,32 +11,6 @@
 class NodeModerationHandler extends ModerationHandler {
 
   /**
-   * The moderation information service.
-   *
-   * @var \Drupal\content_moderation\ModerationInformationInterface
-   */
-  protected $moderationInfo;
-
-  /**
-   * NodeModerationHandler constructor.
-   *
-   * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
-   *   The moderation information service.
-   */
-  public function __construct(ModerationInformationInterface $moderation_info) {
-    $this->moderationInfo = $moderation_info;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
-    return new static(
-      $container->get('content_moderation.moderation_information')
-    );
-  }
-
-  /**
    * {@inheritdoc}
    */
   public function onPresave(ContentEntityInterface $entity, $default_revision, $published_state) {
@@ -67,7 +38,7 @@ public function enforceRevisionsBundleFormAlter(array &$form, FormStateInterface
     /* @var \Drupal\node\Entity\NodeType $entity */
     $entity = $form_state->getFormObject()->getEntity();
 
-    if ($this->moderationInfo->getWorkFlowForEntity($entity)) {
+    if ($entity->getThirdPartySetting('content_moderation', 'enabled', FALSE)) {
       // Force the revision checkbox on.
       $form['workflow']['options']['#default_value']['revision'] = 'revision';
       $form['workflow']['options']['revision']['#disabled'] = TRUE;
diff --git a/core/modules/content_moderation/src/Entity/ModerationState.php b/core/modules/content_moderation/src/Entity/ModerationState.php
new file mode 100644
index 0000000..379ff60
--- /dev/null
+++ b/core/modules/content_moderation/src/Entity/ModerationState.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace Drupal\content_moderation\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\content_moderation\ModerationStateInterface;
+
+/**
+ * Defines the Moderation state entity.
+ *
+ * @ConfigEntityType(
+ *   id = "moderation_state",
+ *   label = @Translation("Moderation state"),
+ *   handlers = {
+ *     "access" = "Drupal\content_moderation\ModerationStateAccessControlHandler",
+ *     "list_builder" = "Drupal\content_moderation\ModerationStateListBuilder",
+ *     "form" = {
+ *       "add" = "Drupal\content_moderation\Form\ModerationStateForm",
+ *       "edit" = "Drupal\content_moderation\Form\ModerationStateForm",
+ *       "delete" = "Drupal\content_moderation\Form\ModerationStateDeleteForm"
+ *     },
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
+ *   },
+ *   config_prefix = "state",
+ *   admin_permission = "administer moderation states",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "label",
+ *     "uuid" = "uuid",
+ *     "weight" = "weight",
+ *   },
+ *   links = {
+ *     "add-form" = "/admin/config/workflow/moderation/states/add",
+ *     "edit-form" = "/admin/config/workflow/moderation/states/{moderation_state}",
+ *     "delete-form" = "/admin/config/workflow/moderation/states/{moderation_state}/delete",
+ *     "collection" = "/admin/config/workflow/moderation/states"
+ *   },
+ *   config_export = {
+ *     "id",
+ *     "label",
+ *     "published",
+ *     "default_revision",
+ *     "weight",
+ *   },
+ * )
+ */
+class ModerationState extends ConfigEntityBase implements ModerationStateInterface {
+
+  /**
+   * The Moderation state ID.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * The Moderation state label.
+   *
+   * @var string
+   */
+  protected $label;
+
+  /**
+   * Whether this state represents a published node.
+   *
+   * @var bool
+   */
+  protected $published;
+
+  /**
+   * Relative weight of this state.
+   *
+   * @var int
+   */
+  protected $weight;
+
+  /**
+   * Whether this state represents a default revision of the node.
+   *
+   * If this is a published state, then this property is ignored.
+   *
+   * @var bool
+   */
+  protected $default_revision;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isPublishedState() {
+    return $this->published;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isDefaultRevisionState() {
+    return $this->published || $this->default_revision;
+  }
+
+}
diff --git a/core/modules/content_moderation/src/Entity/ModerationStateTransition.php b/core/modules/content_moderation/src/Entity/ModerationStateTransition.php
new file mode 100644
index 0000000..95a115b
--- /dev/null
+++ b/core/modules/content_moderation/src/Entity/ModerationStateTransition.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace Drupal\content_moderation\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\content_moderation\ModerationStateTransitionInterface;
+
+/**
+ * Defines the Moderation state transition entity.
+ *
+ * @ConfigEntityType(
+ *   id = "moderation_state_transition",
+ *   label = @Translation("Moderation state transition"),
+ *   handlers = {
+ *     "list_builder" = "Drupal\content_moderation\ModerationStateTransitionListBuilder",
+ *     "form" = {
+ *       "add" = "Drupal\content_moderation\Form\ModerationStateTransitionForm",
+ *       "edit" = "Drupal\content_moderation\Form\ModerationStateTransitionForm",
+ *       "delete" = "Drupal\content_moderation\Form\ModerationStateTransitionDeleteForm"
+ *     },
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
+ *   },
+ *   config_prefix = "state_transition",
+ *   admin_permission = "administer moderation state transitions",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "label",
+ *     "uuid" = "uuid",
+ *     "weight" = "weight"
+ *   },
+ *   links = {
+ *     "add-form" = "/admin/config/workflow/moderation/transitions/add",
+ *     "edit-form" = "/admin/config/workflow/moderation/transitions/{moderation_state_transition}",
+ *     "delete-form" = "/admin/config/workflow/moderation/transitions/{moderation_state_transition}/delete",
+ *     "collection" = "/admin/config/workflow/moderation/transitions"
+ *   }
+ * )
+ */
+class ModerationStateTransition extends ConfigEntityBase implements ModerationStateTransitionInterface {
+
+  /**
+   * The Moderation state transition ID.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * The Moderation state transition label.
+   *
+   * @var string
+   */
+  protected $label;
+
+  /**
+   * ID of from state.
+   *
+   * @var string
+   */
+  protected $stateFrom;
+
+  /**
+   * ID of to state.
+   *
+   * @var string
+   */
+  protected $stateTo;
+
+  /**
+   * Relative weight of this transition.
+   *
+   * @var int
+   */
+  protected $weight;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    parent::calculateDependencies();
+
+    if ($this->stateFrom) {
+      $this->addDependency('config', ModerationState::load($this->stateFrom)->getConfigDependencyName());
+    }
+    if ($this->stateTo) {
+      $this->addDependency('config', ModerationState::load($this->stateTo)->getConfigDependencyName());
+    }
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFromState() {
+    return $this->stateFrom;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getToState() {
+    return $this->stateTo;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWeight() {
+    return $this->weight;
+  }
+
+}
diff --git a/core/modules/content_moderation/src/EntityOperations.php b/core/modules/content_moderation/src/EntityOperations.php
index b4d637f..d76a35b 100644
--- a/core/modules/content_moderation/src/EntityOperations.php
+++ b/core/modules/content_moderation/src/EntityOperations.php
@@ -2,16 +2,14 @@
 
 namespace Drupal\content_moderation;
 
-use Drupal\content_moderation\Entity\ContentModerationState as ContentModerationStateEntity;
+use Drupal\content_moderation\Entity\ContentModerationState;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormBuilderInterface;
 use Drupal\Core\TypedData\TranslatableInterface;
 use Drupal\content_moderation\Form\EntityModerationForm;
-use Drupal\workflows\WorkflowInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -48,13 +46,6 @@ class EntityOperations implements ContainerInjectionInterface {
   protected $tracker;
 
   /**
-   * The entity bundle information service.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
-   */
-  protected $bundleInfo;
-
-  /**
    * Constructs a new EntityOperations object.
    *
    * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
@@ -65,15 +56,12 @@ class EntityOperations implements ContainerInjectionInterface {
    *   The form builder.
    * @param \Drupal\content_moderation\RevisionTrackerInterface $tracker
    *   The revision tracker.
-   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
-   *   The entity bundle information service.
    */
-  public function __construct(ModerationInformationInterface $moderation_info, EntityTypeManagerInterface $entity_type_manager, FormBuilderInterface $form_builder, RevisionTrackerInterface $tracker, EntityTypeBundleInfoInterface $bundle_info) {
+  public function __construct(ModerationInformationInterface $moderation_info, EntityTypeManagerInterface $entity_type_manager, FormBuilderInterface $form_builder, RevisionTrackerInterface $tracker) {
     $this->moderationInfo = $moderation_info;
     $this->entityTypeManager = $entity_type_manager;
     $this->formBuilder = $form_builder;
     $this->tracker = $tracker;
-    $this->bundleInfo = $bundle_info;
   }
 
   /**
@@ -84,8 +72,7 @@ public static function create(ContainerInterface $container) {
       $container->get('content_moderation.moderation_information'),
       $container->get('entity_type.manager'),
       $container->get('form_builder'),
-      $container->get('content_moderation.revision_tracker'),
-      $container->get('entity_type.bundle.info')
+      $container->get('content_moderation.revision_tracker')
     );
   }
 
@@ -99,20 +86,20 @@ public function entityPresave(EntityInterface $entity) {
     if (!$this->moderationInfo->isModeratedEntity($entity)) {
       return;
     }
-
-    if ($entity->moderation_state->value) {
-      $workflow = $this->moderationInfo->getWorkFlowForEntity($entity);
-      /** @var \Drupal\content_moderation\ContentModerationState $current_state */
-      $current_state = $workflow->getState($entity->moderation_state->value);
+    if ($entity->moderation_state->target_id) {
+      $moderation_state = $this->entityTypeManager
+        ->getStorage('moderation_state')
+        ->load($entity->moderation_state->target_id);
+      $published_state = $moderation_state->isPublishedState();
 
       // This entity is default if it is new, the default revision, or the
       // default revision is not published.
       $update_default_revision = $entity->isNew()
-        || $current_state->isDefaultRevisionState()
-        || !$this->isDefaultRevisionPublished($entity, $workflow);
+        || $moderation_state->isDefaultRevisionState()
+        || !$this->isDefaultRevisionPublished($entity);
 
       // Fire per-entity-type logic for handling the save process.
-      $this->entityTypeManager->getHandler($entity->getEntityTypeId(), 'moderation')->onPresave($entity, $update_default_revision, $current_state->isPublishedState());
+      $this->entityTypeManager->getHandler($entity->getEntityTypeId(), 'moderation')->onPresave($entity, $update_default_revision, $published_state);
     }
   }
 
@@ -153,14 +140,15 @@ public function entityUpdate(EntityInterface $entity) {
    *   The entity to update or create a moderation state for.
    */
   protected function updateOrCreateFromEntity(EntityInterface $entity) {
-    $moderation_state = $entity->moderation_state->value;
-    $workflow = $this->moderationInfo->getWorkFlowForEntity($entity);
+    $moderation_state = $entity->moderation_state->target_id;
     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
     if (!$moderation_state) {
-      $moderation_state = $workflow->getInitialState()->id();
+      $moderation_state = $this->entityTypeManager
+        ->getStorage($entity->getEntityType()->getBundleEntityType())->load($entity->bundle())
+        ->getThirdPartySetting('content_moderation', 'default_moderation_state');
     }
 
-    // @todo what if $entity->moderation_state is null at this point?
+    // @todo what if $entity->moderation_state->target_id is null at this point?
     $entity_type_id = $entity->getEntityTypeId();
     $entity_id = $entity->id();
     $entity_revision_id = $entity->getRevisionId();
@@ -169,7 +157,6 @@ protected function updateOrCreateFromEntity(EntityInterface $entity) {
     $entities = $storage->loadByProperties([
       'content_entity_type_id' => $entity_type_id,
       'content_entity_id' => $entity_id,
-      'workflow' => $workflow->id(),
     ]);
 
     /** @var \Drupal\content_moderation\ContentModerationStateInterface $content_moderation_state */
@@ -179,7 +166,6 @@ protected function updateOrCreateFromEntity(EntityInterface $entity) {
         'content_entity_type_id' => $entity_type_id,
         'content_entity_id' => $entity_id,
       ]);
-      $content_moderation_state->workflow->target_id = $workflow->id();
     }
     else {
       // Create a new revision.
@@ -200,7 +186,7 @@ protected function updateOrCreateFromEntity(EntityInterface $entity) {
     // Create the ContentModerationState entity for the inserted entity.
     $content_moderation_state->set('content_entity_revision_id', $entity_revision_id);
     $content_moderation_state->set('moderation_state', $moderation_state);
-    ContentModerationStateEntity::updateOrCreateFromEntity($content_moderation_state);
+    ContentModerationState::updateOrCreateFromEntity($content_moderation_state);
   }
 
   /**
@@ -255,13 +241,11 @@ public function entityView(array &$build, EntityInterface $entity, EntityViewDis
    *
    * @param \Drupal\Core\Entity\EntityInterface $entity
    *   The entity being saved.
-   * @param \Drupal\workflows\WorkflowInterface $workflow
-   *   The workflow being applied to the entity.
    *
    * @return bool
    *   TRUE if the default revision is published. FALSE otherwise.
    */
-  protected function isDefaultRevisionPublished(EntityInterface $entity, WorkflowInterface $workflow) {
+  protected function isDefaultRevisionPublished(EntityInterface $entity) {
     $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
     $default_revision = $storage->load($entity->id());
 
@@ -276,7 +260,7 @@ protected function isDefaultRevisionPublished(EntityInterface $entity, WorkflowI
       $default_revision = $default_revision->getTranslation($entity->language()->getId());
     }
 
-    return $default_revision && $workflow->getState($default_revision->moderation_state->value)->isPublishedState();
+    return $default_revision && $default_revision->moderation_state->entity->isPublishedState();
   }
 
 }
diff --git a/core/modules/content_moderation/src/EntityTypeInfo.php b/core/modules/content_moderation/src/EntityTypeInfo.php
index 25abf6a..c35919e 100644
--- a/core/modules/content_moderation/src/EntityTypeInfo.php
+++ b/core/modules/content_moderation/src/EntityTypeInfo.php
@@ -9,7 +9,6 @@
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -51,13 +50,6 @@ class EntityTypeInfo implements ContainerInjectionInterface {
   protected $entityTypeManager;
 
   /**
-   * The bundle information service.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
-   */
-  protected $bundleInfo;
-
-  /**
    * The current user.
    *
    * @var \Drupal\Core\Session\AccountInterface
@@ -85,16 +77,11 @@ class EntityTypeInfo implements ContainerInjectionInterface {
    *   The moderation information service.
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    *   Entity type manager.
-   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
-   *   Bundle information service.
-   * @param \Drupal\Core\Session\AccountInterface $current_user
-   *   Current user.
    */
-  public function __construct(TranslationInterface $translation, ModerationInformationInterface $moderation_information, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $bundle_info, AccountInterface $current_user) {
+  public function __construct(TranslationInterface $translation, ModerationInformationInterface $moderation_information, EntityTypeManagerInterface $entity_type_manager, AccountInterface $current_user) {
     $this->stringTranslation = $translation;
     $this->moderationInfo = $moderation_information;
     $this->entityTypeManager = $entity_type_manager;
-    $this->bundleInfo = $bundle_info;
     $this->currentUser = $current_user;
   }
 
@@ -106,7 +93,6 @@ public static function create(ContainerInterface $container) {
       $container->get('string_translation'),
       $container->get('content_moderation.moderation_information'),
       $container->get('entity_type.manager'),
-      $container->get('entity_type.bundle.info'),
       $container->get('current_user')
     );
   }
@@ -210,7 +196,7 @@ public function entityOperation(EntityInterface $entity) {
     $operations = [];
     $type = $entity->getEntityType();
     $bundle_of = $type->getBundleOf();
-    if ($this->currentUser->hasPermission('administer content moderation') && $bundle_of &&
+    if ($this->currentUser->hasPermission('administer moderation states') && $bundle_of &&
       $this->moderationInfo->canModerateEntitiesOfEntityType($this->entityTypeManager->getDefinition($bundle_of))
     ) {
       $operations['manage-moderation'] = [
@@ -276,12 +262,16 @@ public function entityExtraFieldInfo() {
    *   - bundle: The machine name of a bundle, such as "page" or "article".
    */
   protected function getModeratedBundles() {
-    $entity_types = array_filter($this->entityTypeManager->getDefinitions(), [$this->moderationInfo, 'canModerateEntitiesOfEntityType']);
-    foreach ($entity_types as $type_name => $type) {
-      foreach ($this->bundleInfo->getBundleInfo($type_name) as $bundle_id => $bundle) {
-        if ($this->moderationInfo->shouldModerateEntitiesOfBundle($type, $bundle_id)) {
-          yield ['entity' => $type_name, 'bundle' => $bundle_id];
-        }
+    /** @var ConfigEntityTypeInterface $type */
+    foreach ($this->filterNonRevisionableEntityTypes($this->entityTypeManager->getDefinitions()) as $type_name => $type) {
+      $result = $this->entityTypeManager
+        ->getStorage($type_name)
+        ->getQuery()
+        ->condition('third_party_settings.content_moderation.enabled', TRUE)
+        ->execute();
+
+      foreach ($result as $bundle_name) {
+        yield ['entity' => $type->getBundleOf(), 'bundle' => $bundle_name];
       }
     }
   }
@@ -301,9 +291,9 @@ public function entityBaseFieldInfo(EntityTypeInterface $entity_type) {
     }
 
     $fields = [];
-    $fields['moderation_state'] = BaseFieldDefinition::create('string')
-      ->setLabel(t('Moderation state'))
-      ->setDescription(t('The moderation state of this piece of content.'))
+    $fields['moderation_state'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel($this->t('Moderation state'))
+      ->setDescription($this->t('The moderation state of this piece of content.'))
       ->setComputed(TRUE)
       ->setClass(ModerationStateFieldItemList::class)
       ->setSetting('target_type', 'moderation_state')
diff --git a/core/modules/content_moderation/src/Form/BundleModerationConfigurationForm.php b/core/modules/content_moderation/src/Form/BundleModerationConfigurationForm.php
index 10b3d6a..88bbf7f 100644
--- a/core/modules/content_moderation/src/Form/BundleModerationConfigurationForm.php
+++ b/core/modules/content_moderation/src/Form/BundleModerationConfigurationForm.php
@@ -2,11 +2,12 @@
 
 namespace Drupal\content_moderation\Form;
 
-use Drupal\content_moderation\Plugin\WorkflowType\ContentModeration;
-use Drupal\workflows\WorkflowInterface;
+use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
 use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\content_moderation\Entity\ModerationState;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -49,94 +50,146 @@ public function getBaseFormId() {
    * {@inheritdoc}
    */
   public function form(array $form, FormStateInterface $form_state) {
-    /* @var \Drupal\Core\Config\Entity\ConfigEntityInterface $bundle */
-    $bundle = $this->getEntity();
-    $bundle_of_entity_type = $this->entityTypeManager->getDefinition($bundle->getEntityType()->getBundleOf());
-    /* @var \Drupal\workflows\WorkflowInterface[] $workflows */
-    $workflows = $this->entityTypeManager->getStorage('workflow')->loadMultiple();
-
-    $options = array_map(function (WorkflowInterface $workflow) {
-      return $workflow->label();
-    }, array_filter($workflows, function (WorkflowInterface $workflow) {
-      return $workflow->status() && $workflow->getTypePlugin() instanceof ContentModeration;
+    /* @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $bundle */
+    $bundle = $form_state->getFormObject()->getEntity();
+    $form['enable_moderation_state'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Enable moderation states.'),
+      '#description' => $this->t('Content of this type must transition through moderation states in order to be published.'),
+      '#default_value' => $bundle->getThirdPartySetting('content_moderation', 'enabled', FALSE),
+    ];
+
+    // Add a special message when moderation is being disabled.
+    if ($bundle->getThirdPartySetting('content_moderation', 'enabled', FALSE)) {
+      $form['enable_moderation_state_note'] = [
+        '#type' => 'item',
+        '#description' => $this->t('After disabling moderation, any existing forward drafts will be accessible via the "Revisions" tab.'),
+        '#states' => [
+          'visible' => [
+            ':input[name=enable_moderation_state]' => ['checked' => FALSE],
+          ],
+        ],
+      ];
+    }
+
+    $states = $this->entityTypeManager->getStorage('moderation_state')->loadMultiple();
+    $label = function(ModerationState $state) {
+      return $state->label();
+    };
+
+    $options_published = array_map($label, array_filter($states, function(ModerationState $state) {
+      return $state->isPublishedState();
     }));
 
-    $selected_workflow = array_reduce($workflows, function ($carry, WorkflowInterface $workflow) use ($bundle_of_entity_type, $bundle) {
-      $plugin = $workflow->getTypePlugin();
-      if ($plugin instanceof ContentModeration && $plugin->appliesToEntityTypeAndBundle($bundle_of_entity_type->id(), $bundle->id())) {
-        return $workflow->id();
-      }
-      return $carry;
-    });
-    $form['workflow'] = [
-      '#type' => 'select',
-      '#title' => $this->t('Select the workflow to apply'),
-      '#default_value' => $selected_workflow,
-      '#options' => $options,
-      '#required' => FALSE,
-      '#empty_value' => '',
-    ];
+    $options_unpublished = array_map($label, array_filter($states, function(ModerationState $state) {
+      return !$state->isPublishedState();
+    }));
 
-    $form['original_workflow'] = [
-      '#type' => 'value',
-      '#value' => $selected_workflow,
+    $form['allowed_moderation_states_unpublished'] = [
+      '#type' => 'checkboxes',
+      '#title' => $this->t('Allowed moderation states (Unpublished)'),
+      '#description' => $this->t('The allowed unpublished moderation states this content-type can be assigned.'),
+      '#default_value' => $bundle->getThirdPartySetting('content_moderation', 'allowed_moderation_states', array_keys($options_unpublished)),
+      '#options' => $options_unpublished,
+      '#required' => TRUE,
+      '#states' => [
+        'visible' => [
+          ':input[name=enable_moderation_state]' => ['checked' => TRUE],
+        ],
+      ],
     ];
 
-    $form['bundle'] = [
-      '#type' => 'value',
-      '#value' => $bundle->id(),
+    $form['allowed_moderation_states_published'] = [
+      '#type' => 'checkboxes',
+      '#title' => $this->t('Allowed moderation states (Published)'),
+      '#description' => $this->t('The allowed published moderation states this content-type can be assigned.'),
+      '#default_value' => $bundle->getThirdPartySetting('content_moderation', 'allowed_moderation_states', array_keys($options_published)),
+      '#options' => $options_published,
+      '#required' => TRUE,
+      '#states' => [
+        'visible' => [
+          ':input[name=enable_moderation_state]' => ['checked' => TRUE],
+        ],
+      ],
     ];
 
-    $form['entity_type'] = [
-      '#type' => 'value',
-      '#value' => $bundle_of_entity_type->id(),
+    // The key of the array needs to be a user-facing string so we have to fully
+    // render the translatable string to a real string, or else PHP errors on an
+    // object used as an array key.
+    $options = [
+      $this->t('Unpublished')->render() => $options_unpublished,
+      $this->t('Published')->render() => $options_published,
     ];
 
-    // Add a special message when moderation is being disabled.
-    if ($selected_workflow) {
-      $form['enable_workflow_note'] = [
-        '#type' => 'item',
-        '#description' => $this->t('After disabling moderation, any existing forward drafts will be accessible via the "Revisions" tab.'),
-        '#access' => !empty($selected_workflow)
-      ];
-    }
+    $form['default_moderation_state'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Default moderation state'),
+      '#options' => $options,
+      '#description' => $this->t('Select the moderation state for new content'),
+      '#default_value' => $bundle->getThirdPartySetting('content_moderation', 'default_moderation_state', 'draft'),
+      '#states' => [
+        'visible' => [
+          ':input[name=enable_moderation_state]' => ['checked' => TRUE],
+        ],
+      ],
+    ];
+    $form['#entity_builders'][] = '::formBuilderCallback';
 
     return parent::form($form, $form_state);
   }
 
   /**
-   * {@inheritdoc}
+   * Form builder callback.
+   *
+   * @todo This should be folded into the form method.
+   *
+   * @param string $entity_type_id
+   *   The entity type identifier.
+   * @param \Drupal\Core\Entity\EntityInterface $bundle
+   *   The bundle entity updated with the submitted values.
+   * @param array $form
+   *   The complete form array.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
    */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    // If moderation is enabled, revisions MUST be enabled as well. Otherwise we
-    // can't have forward revisions.
-    drupal_set_message($this->t('Your settings have been saved.'));
+  public function formBuilderCallback($entity_type_id, EntityInterface $bundle, &$form, FormStateInterface $form_state) {
+    // @todo https://www.drupal.org/node/2779933 write a test for this.
+    if ($bundle instanceof ThirdPartySettingsInterface) {
+      $bundle->setThirdPartySetting('content_moderation', 'enabled', $form_state->getValue('enable_moderation_state'));
+      $bundle->setThirdPartySetting('content_moderation', 'allowed_moderation_states', array_keys(array_filter($form_state->getValue('allowed_moderation_states_published') + $form_state->getValue('allowed_moderation_states_unpublished'))));
+      $bundle->setThirdPartySetting('content_moderation', 'default_moderation_state', $form_state->getValue('default_moderation_state'));
+    }
   }
 
   /**
    * {@inheritdoc}
    */
-  public function save(array $form, FormStateInterface $form_state) {
-    $entity_type_id = $form_state->getValue('entity_type');
-    $bundle_id = $form_state->getValue('bundle');
-    $new_workflow_id = $form_state->getValue('workflow');
-    $original_workflow_id = $form_state->getValue('original_workflow');
-    if ($new_workflow_id === $original_workflow_id) {
-      // Nothing to do.
-      return;
-    }
-    if ($original_workflow_id) {
-      /* @var \Drupal\workflows\WorkflowInterface $workflow */
-      $workflow = $this->entityTypeManager->getStorage('workflow')->load($original_workflow_id);
-      $workflow->getTypePlugin()->removeEntityTypeAndBundle($entity_type_id, $bundle_id);
-      $workflow->save();
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    if ($form_state->getValue('enable_moderation_state')) {
+      $allowed = array_keys(array_filter($form_state->getValue('allowed_moderation_states_published') + $form_state->getValue('allowed_moderation_states_unpublished')));
+
+      if (($default = $form_state->getValue('default_moderation_state')) && !in_array($default, $allowed, TRUE)) {
+        $form_state->setErrorByName('default_moderation_state', $this->t('The default moderation state must be one of the allowed states.'));
+      }
     }
-    if ($new_workflow_id) {
-      /* @var \Drupal\workflows\WorkflowInterface $workflow */
-      $workflow = $this->entityTypeManager->getStorage('workflow')->load($new_workflow_id);
-      $workflow->getTypePlugin()->addEntityTypeAndBundle($entity_type_id, $bundle_id);
-      $workflow->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // If moderation is enabled, revisions MUST be enabled as well. Otherwise we
+    // can't have forward revisions.
+    if ($form_state->getValue('enable_moderation_state')) {
+      /* @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $bundle */
+      $bundle = $form_state->getFormObject()->getEntity();
+
+      $this->entityTypeManager->getHandler($bundle->getEntityType()->getBundleOf(), 'moderation')->onBundleModerationConfigurationFormSubmit($bundle);
     }
+
+    parent::submitForm($form, $form_state);
+
+    drupal_set_message($this->t('Your settings have been saved.'));
   }
 
 }
diff --git a/core/modules/content_moderation/src/Form/EntityModerationForm.php b/core/modules/content_moderation/src/Form/EntityModerationForm.php
index 6f9de1f..39baec0 100644
--- a/core/modules/content_moderation/src/Form/EntityModerationForm.php
+++ b/core/modules/content_moderation/src/Form/EntityModerationForm.php
@@ -3,11 +3,12 @@
 namespace Drupal\content_moderation\Form;
 
 use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\content_moderation\Entity\ModerationStateTransition;
 use Drupal\content_moderation\ModerationInformationInterface;
 use Drupal\content_moderation\StateTransitionValidation;
-use Drupal\workflows\Transition;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -30,16 +31,26 @@ class EntityModerationForm extends FormBase {
   protected $validation;
 
   /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
    * EntityModerationForm constructor.
    *
    * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
    *   The moderation information service.
    * @param \Drupal\content_moderation\StateTransitionValidation $validation
    *   The moderation state transition validation service.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
    */
-  public function __construct(ModerationInformationInterface $moderation_info, StateTransitionValidation $validation) {
+  public function __construct(ModerationInformationInterface $moderation_info, StateTransitionValidation $validation, EntityTypeManagerInterface $entity_type_manager) {
     $this->moderationInfo = $moderation_info;
     $this->validation = $validation;
+    $this->entityTypeManager = $entity_type_manager;
   }
 
   /**
@@ -48,7 +59,8 @@ public function __construct(ModerationInformationInterface $moderation_info, Sta
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('content_moderation.moderation_information'),
-      $container->get('content_moderation.state_transition_validation')
+      $container->get('content_moderation.state_transition_validation'),
+      $container->get('entity_type.manager')
     );
   }
 
@@ -63,21 +75,20 @@ public function getFormId() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state, ContentEntityInterface $entity = NULL) {
-    $current_state = $entity->moderation_state->value;
-    $workflow = $this->moderationInfo->getWorkFlowForEntity($entity);
+    /** @var \Drupal\content_moderation\Entity\ModerationState $current_state */
+    $current_state = $entity->moderation_state->entity;
 
-    /** @var \Drupal\workflows\Transition[] $transitions */
     $transitions = $this->validation->getValidTransitions($entity, $this->currentUser());
 
     // Exclude self-transitions.
-    $transitions = array_filter($transitions, function(Transition $transition) use ($current_state) {
-      return $transition->to()->id() != $current_state;
+    $transitions = array_filter($transitions, function(ModerationStateTransition $transition) use ($current_state) {
+      return $transition->getToState() != $current_state->id();
     });
 
     $target_states = [];
-
+    /** @var ModerationStateTransition $transition */
     foreach ($transitions as $transition) {
-      $target_states[$transition->to()->id()] = $transition->to()->label();
+      $target_states[$transition->getToState()] = $transition->label();
     }
 
     if (!count($target_states)) {
@@ -88,7 +99,7 @@ public function buildForm(array $form, FormStateInterface $form_state, ContentEn
       $form['current'] = [
         '#type' => 'item',
         '#title' => $this->t('Status'),
-        '#markup' => $workflow->getState($current_state)->label(),
+        '#markup' => $current_state->label(),
       ];
     }
 
@@ -128,19 +139,21 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
     // @todo should we just just be updating the content moderation state
     //   entity? That would prevent setting the revision log.
-    $entity->set('moderation_state', $new_state);
+    $entity->moderation_state->target_id = $new_state;
     $entity->revision_log = $form_state->getValue('revision_log');
 
     $entity->save();
 
     drupal_set_message($this->t('The moderation state has been updated.'));
 
-    $new_state = $this->moderationInfo->getWorkFlowForEntity($entity)->getState($new_state);
+    /** @var \Drupal\content_moderation\Entity\ModerationState $state */
+    $state = $this->entityTypeManager->getStorage('moderation_state')->load($new_state);
+
     // The page we're on likely won't be visible if we just set the entity to
     // the default state, as we hide that latest-revision tab if there is no
     // forward revision. Redirect to the canonical URL instead, since that will
     // still exist.
-    if ($new_state->isDefaultRevisionState()) {
+    if ($state->isDefaultRevisionState()) {
       $form_state->setRedirectUrl($entity->toUrl('canonical'));
     }
   }
diff --git a/core/modules/workflows/src/Form/WorkflowDeleteForm.php b/core/modules/content_moderation/src/Form/ModerationStateDeleteForm.php
similarity index 64%
rename from core/modules/workflows/src/Form/WorkflowDeleteForm.php
rename to core/modules/content_moderation/src/Form/ModerationStateDeleteForm.php
index b9b8331..43e2b36 100644
--- a/core/modules/workflows/src/Form/WorkflowDeleteForm.php
+++ b/core/modules/content_moderation/src/Form/ModerationStateDeleteForm.php
@@ -1,28 +1,28 @@
 <?php
 
-namespace Drupal\workflows\Form;
+namespace Drupal\content_moderation\Form;
 
 use Drupal\Core\Entity\EntityConfirmFormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
 
 /**
- * Builds the form to delete Workflow entities.
+ * Builds the form to delete Moderation state entities.
  */
-class WorkflowDeleteForm extends EntityConfirmFormBase {
+class ModerationStateDeleteForm extends EntityConfirmFormBase {
 
   /**
    * {@inheritdoc}
    */
   public function getQuestion() {
-    return $this->t('Are you sure you want to delete %name?', ['%name' => $this->entity->label()]);
+    return $this->t('Are you sure you want to delete %name?', array('%name' => $this->entity->label()));
   }
 
   /**
    * {@inheritdoc}
    */
   public function getCancelUrl() {
-    return new Url('entity.workflow.collection');
+    return new Url('entity.moderation_state.collection');
   }
 
   /**
@@ -39,7 +39,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     $this->entity->delete();
 
     drupal_set_message($this->t(
-      'Workflow %label deleted.',
+      'Moderation state %label deleted.',
       ['%label' => $this->entity->label()]
     ));
 
diff --git a/core/modules/content_moderation/src/Form/ModerationStateForm.php b/core/modules/content_moderation/src/Form/ModerationStateForm.php
new file mode 100644
index 0000000..32d7a48
--- /dev/null
+++ b/core/modules/content_moderation/src/Form/ModerationStateForm.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Drupal\content_moderation\Form;
+
+use Drupal\content_moderation\Entity\ModerationState;
+use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Class ModerationStateForm.
+ */
+class ModerationStateForm extends EntityForm {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, FormStateInterface $form_state) {
+    $form = parent::form($form, $form_state);
+
+    /* @var \Drupal\content_moderation\ModerationStateInterface $moderation_state */
+    $moderation_state = $this->entity;
+    $form['label'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Label'),
+      '#maxlength' => 255,
+      '#default_value' => $moderation_state->label(),
+      '#description' => $this->t('Label for the Moderation state.'),
+      '#required' => TRUE,
+    );
+
+    $form['id'] = array(
+      '#type' => 'machine_name',
+      '#default_value' => $moderation_state->id(),
+      '#machine_name' => array(
+        'exists' => [ModerationState::class, 'load'],
+      ),
+      '#disabled' => !$moderation_state->isNew(),
+    );
+
+    $form['published'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Published'),
+      '#description' => $this->t('When content reaches this state it should be published.'),
+      '#default_value' => $moderation_state->isPublishedState(),
+    ];
+
+    $form['default_revision'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Default revision'),
+      '#description' => $this->t('When content reaches this state it should be made the default revision; this is implied for published states.'),
+      '#default_value' => $moderation_state->isDefaultRevisionState(),
+      // @todo Add form #state to force "make default" on when "published" is
+      // on for a state.
+      // @see https://www.drupal.org/node/2645614
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    $moderation_state = $this->entity;
+    $status = $moderation_state->save();
+
+    switch ($status) {
+      case SAVED_NEW:
+        drupal_set_message($this->t('Created the %label Moderation state.', [
+          '%label' => $moderation_state->label(),
+        ]));
+        break;
+
+      default:
+        drupal_set_message($this->t('Saved the %label Moderation state.', [
+          '%label' => $moderation_state->label(),
+        ]));
+    }
+    $form_state->setRedirectUrl($moderation_state->toUrl('collection'));
+  }
+
+}
diff --git a/core/modules/content_moderation/src/Form/ModerationStateTransitionDeleteForm.php b/core/modules/content_moderation/src/Form/ModerationStateTransitionDeleteForm.php
new file mode 100644
index 0000000..f153f1f
--- /dev/null
+++ b/core/modules/content_moderation/src/Form/ModerationStateTransitionDeleteForm.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Drupal\content_moderation\Form;
+
+use Drupal\Core\Entity\EntityConfirmFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+
+/**
+ * Builds the form to delete Moderation state transition entities.
+ */
+class ModerationStateTransitionDeleteForm extends EntityConfirmFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    return $this->t('Are you sure you want to delete %name?', array('%name' => $this->entity->label()));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCancelUrl() {
+    return new Url('entity.moderation_state_transition.collection');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfirmText() {
+    return $this->t('Delete');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $this->entity->delete();
+
+    drupal_set_message($this->t(
+      'Moderation transition %label deleted.',
+      ['%label' => $this->entity->label()]
+    ));
+
+    $form_state->setRedirectUrl($this->getCancelUrl());
+  }
+
+}
diff --git a/core/modules/content_moderation/src/Form/ModerationStateTransitionForm.php b/core/modules/content_moderation/src/Form/ModerationStateTransitionForm.php
new file mode 100644
index 0000000..8322c18
--- /dev/null
+++ b/core/modules/content_moderation/src/Form/ModerationStateTransitionForm.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Drupal\content_moderation\Form;
+
+use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\Query\QueryFactory;
+use Drupal\Core\Form\FormStateInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Class ModerationStateTransitionForm.
+ *
+ * @package Drupal\content_moderation\Form
+ */
+class ModerationStateTransitionForm extends EntityForm {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The entity query factory.
+   *
+   * @var \Drupal\Core\Entity\Query\QueryFactory
+   */
+  protected $queryFactory;
+
+  /**
+   * Constructs a new ModerationStateTransitionForm.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
+   *   The entity query factory.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, QueryFactory $query_factory) {
+    $this->entityTypeManager = $entity_type_manager;
+    $this->queryFactory = $query_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('entity_type.manager'), $container->get('entity.query'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, FormStateInterface $form_state) {
+    $form = parent::form($form, $form_state);
+
+    /* @var \Drupal\content_moderation\ModerationStateTransitionInterface $moderation_state_transition */
+    $moderation_state_transition = $this->entity;
+    $form['label'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Label'),
+      '#maxlength' => 255,
+      '#default_value' => $moderation_state_transition->label(),
+      '#description' => $this->t('Label for the Moderation state transition.'),
+      '#required' => TRUE,
+    ];
+
+    $form['id'] = [
+      '#type' => 'machine_name',
+      '#default_value' => $moderation_state_transition->id(),
+      '#machine_name' => [
+        'exists' => '\Drupal\content_moderation\Entity\ModerationStateTransition::load',
+      ],
+      '#disabled' => !$moderation_state_transition->isNew(),
+    ];
+
+    $options = [];
+    foreach ($this->entityTypeManager->getStorage('moderation_state')
+               ->loadMultiple() as $moderation_state) {
+      $options[$moderation_state->id()] = $moderation_state->label();
+    }
+
+    $form['container'] = [
+      '#type' => 'container',
+      '#attributes' => [
+        'class' => ['container-inline'],
+      ],
+    ];
+
+    $form['container']['stateFrom'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Transition from'),
+      '#options' => $options,
+      '#required' => TRUE,
+      '#empty_option' => $this->t('-- Select --'),
+      '#default_value' => $moderation_state_transition->getFromState(),
+    ];
+
+    $form['container']['stateTo'] = [
+      '#type' => 'select',
+      '#options' => $options,
+      '#required' => TRUE,
+      '#title' => $this->t('Transition to'),
+      '#empty_option' => $this->t('-- Select --'),
+      '#default_value' => $moderation_state_transition->getToState(),
+    ];
+
+    // Make sure there's always at least a wide enough delta on weight to cover
+    // the current value or the total number of transitions. That way we
+    // never end up forcing a transition to change its weight needlessly.
+    $num_transitions = $this->queryFactory->get('moderation_state_transition')
+      ->count()
+      ->execute();
+    $delta = max(abs($moderation_state_transition->getWeight()), $num_transitions);
+
+    $form['weight'] = [
+      '#type' => 'weight',
+      '#delta' => $delta,
+      '#options' => $options,
+      '#title' => $this->t('Weight'),
+      '#default_value' => $moderation_state_transition->getWeight(),
+      '#description' => $this->t('Orders the transitions in moderation forms and the administrative listing. Heavier items will sink and the lighter items will be positioned nearer the top.'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    $moderation_state_transition = $this->entity;
+    $status = $moderation_state_transition->save();
+
+    switch ($status) {
+      case SAVED_NEW:
+        drupal_set_message($this->t('Created the %label Moderation state transition.', [
+          '%label' => $moderation_state_transition->label(),
+        ]));
+        break;
+
+      default:
+        drupal_set_message($this->t('Saved the %label Moderation state transition.', [
+          '%label' => $moderation_state_transition->label(),
+        ]));
+    }
+    $form_state->setRedirectUrl($moderation_state_transition->toUrl('collection'));
+  }
+
+}
diff --git a/core/modules/content_moderation/src/ModerationInformation.php b/core/modules/content_moderation/src/ModerationInformation.php
index e8ebf39..ed33902 100644
--- a/core/modules/content_moderation/src/ModerationInformation.php
+++ b/core/modules/content_moderation/src/ModerationInformation.php
@@ -4,7 +4,6 @@
 
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 
@@ -21,23 +20,15 @@ class ModerationInformation implements ModerationInformationInterface {
   protected $entityTypeManager;
 
   /**
-   * The bundle information service.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
-   */
-  protected $bundleInfo;
-
-  /**
    * Creates a new ModerationInformation instance.
    *
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    *   The entity type manager.
-   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
-   *   The bundle information service.
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $bundle_info) {
+  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
     $this->entityTypeManager = $entity_type_manager;
-    $this->bundleInfo = $bundle_info;
   }
 
   /**
@@ -63,8 +54,10 @@ public function canModerateEntitiesOfEntityType(EntityTypeInterface $entity_type
    */
   public function shouldModerateEntitiesOfBundle(EntityTypeInterface $entity_type, $bundle) {
     if ($this->canModerateEntitiesOfEntityType($entity_type)) {
-      $bundles = $this->bundleInfo->getBundleInfo($entity_type->id());
-      return isset($bundles[$bundle]['workflow']);
+      $bundle_entity = $this->entityTypeManager->getStorage($entity_type->getBundleEntityType())->load($bundle);
+      if ($bundle_entity) {
+        return $bundle_entity->getThirdPartySetting('content_moderation', 'enabled', FALSE);
+      }
     }
     return FALSE;
   }
@@ -130,22 +123,10 @@ public function hasForwardRevision(ContentEntityInterface $entity) {
    * {@inheritdoc}
    */
   public function isLiveRevision(ContentEntityInterface $entity) {
-    $workflow = $this->getWorkFlowForEntity($entity);
     return $this->isLatestRevision($entity)
       && $entity->isDefaultRevision()
-      && $entity->moderation_state->value
-      && $workflow->getState($entity->moderation_state->value)->isPublishedState();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getWorkFlowForEntity(ContentEntityInterface $entity) {
-    $bundles = $this->bundleInfo->getBundleInfo($entity->getEntityTypeId());
-    if (isset($bundles[$entity->bundle()]['workflow'])) {
-      return $this->entityTypeManager->getStorage('workflow')->load($bundles[$entity->bundle()]['workflow']);
-    };
-    return NULL;
+      && $entity->moderation_state->entity
+      && $entity->moderation_state->entity->isPublishedState();
   }
 
 }
diff --git a/core/modules/content_moderation/src/ModerationInformationInterface.php b/core/modules/content_moderation/src/ModerationInformationInterface.php
index 531057e..95a658e 100644
--- a/core/modules/content_moderation/src/ModerationInformationInterface.php
+++ b/core/modules/content_moderation/src/ModerationInformationInterface.php
@@ -126,15 +126,4 @@ public function hasForwardRevision(ContentEntityInterface $entity);
    */
   public function isLiveRevision(ContentEntityInterface $entity);
 
-  /**
-   * Gets the workflow for the given content entity.
-   *
-   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
-   *   The content entity to get the workflow for.
-   *
-   * @return \Drupal\workflows\WorkflowInterface|null
-   *   The workflow entity. NULL if there is no workflow.
-   */
-  public function getWorkFlowForEntity(ContentEntityInterface $entity);
-
 }
diff --git a/core/modules/content_moderation/src/ModerationStateAccessControlHandler.php b/core/modules/content_moderation/src/ModerationStateAccessControlHandler.php
new file mode 100644
index 0000000..b2c86d7
--- /dev/null
+++ b/core/modules/content_moderation/src/ModerationStateAccessControlHandler.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\content_moderation;
+
+use Drupal\Core\Entity\EntityAccessControlHandler;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Access\AccessResult;
+
+/**
+ * Access controller for the Moderation State entity.
+ *
+ * @see \Drupal\workbench_moderation\Entity\ModerationState.
+ */
+class ModerationStateAccessControlHandler extends EntityAccessControlHandler {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
+    $admin_access = parent::checkAccess($entity, $operation, $account);
+
+    // Allow view with other permission.
+    if ($operation === 'view') {
+      return AccessResult::allowedIfHasPermission($account, 'view moderation states')->orIf($admin_access);
+    }
+
+    return $admin_access;
+  }
+
+}
diff --git a/core/modules/content_moderation/src/ModerationStateInterface.php b/core/modules/content_moderation/src/ModerationStateInterface.php
new file mode 100644
index 0000000..99f664f
--- /dev/null
+++ b/core/modules/content_moderation/src/ModerationStateInterface.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\content_moderation;
+
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+
+/**
+ * Provides an interface for defining Moderation state entities.
+ */
+interface ModerationStateInterface extends ConfigEntityInterface {
+
+  /**
+   * Determines if content updated to this state should be published.
+   *
+   * @return bool
+   *   TRUE if content updated to this state should be published.
+   */
+  public function isPublishedState();
+
+  /**
+   * Determines if content updated to this state should be the default revision.
+   *
+   * @return bool
+   *   TRUE if content in this state should be the default revision.
+   */
+  public function isDefaultRevisionState();
+
+}
diff --git a/core/modules/content_moderation/src/ModerationStateListBuilder.php b/core/modules/content_moderation/src/ModerationStateListBuilder.php
new file mode 100644
index 0000000..05ba513
--- /dev/null
+++ b/core/modules/content_moderation/src/ModerationStateListBuilder.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\content_moderation;
+
+use Drupal\Core\Config\Entity\DraggableListBuilder;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Provides a listing of Moderation state entities.
+ */
+class ModerationStateListBuilder extends DraggableListBuilder {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'moderation_state_admin_overview_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildHeader() {
+    $header['label'] = $this->t('Moderation state');
+    $header['id'] = $this->t('Machine name');
+
+    return $header + parent::buildHeader();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRow(EntityInterface $entity) {
+    $row['label'] = $entity->label();
+    $row['id']['#markup'] = $entity->id();
+
+    return $row + parent::buildRow($entity);
+  }
+
+}
diff --git a/core/modules/content_moderation/src/ModerationStateTransitionInterface.php b/core/modules/content_moderation/src/ModerationStateTransitionInterface.php
new file mode 100644
index 0000000..91b5b13
--- /dev/null
+++ b/core/modules/content_moderation/src/ModerationStateTransitionInterface.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\content_moderation;
+
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+
+/**
+ * Provides an interface for defining Moderation state transition entities.
+ */
+interface ModerationStateTransitionInterface extends ConfigEntityInterface {
+
+  /**
+   * Gets the from state for the given transition.
+   *
+   * @return string
+   *   The moderation state ID for the from state.
+   */
+  public function getFromState();
+
+  /**
+   * Gets the to state for the given transition.
+   *
+   * @return string
+   *   The moderation state ID for the to state.
+   */
+  public function getToState();
+
+  /**
+   * Gets the weight for the given transition.
+   *
+   * @return int
+   *   The weight of this transition.
+   */
+  public function getWeight();
+
+}
diff --git a/core/modules/content_moderation/src/ModerationStateTransitionListBuilder.php b/core/modules/content_moderation/src/ModerationStateTransitionListBuilder.php
new file mode 100644
index 0000000..577283e
--- /dev/null
+++ b/core/modules/content_moderation/src/ModerationStateTransitionListBuilder.php
@@ -0,0 +1,173 @@
+<?php
+
+namespace Drupal\content_moderation;
+
+use Drupal\Core\Config\Entity\DraggableListBuilder;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\user\RoleStorageInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a listing of Moderation state transition entities.
+ */
+class ModerationStateTransitionListBuilder extends DraggableListBuilder {
+
+  /**
+   * Moderation state entity storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $stateStorage;
+
+  /**
+   * The role storage.
+   *
+   * @var \Drupal\user\RoleStorageInterface
+   */
+  protected $roleStorage;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $entity_type,
+      $container->get('entity.manager')->getStorage($entity_type->id()),
+      $container->get('entity.manager')->getStorage('moderation_state'),
+      $container->get('entity.manager')->getStorage('user_role')
+    );
+  }
+
+  /**
+   * Constructs a new ModerationStateTransitionListBuilder.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   Entity Type.
+   * @param \Drupal\Core\Entity\EntityStorageInterface $transition_storage
+   *   Moderation state transition entity storage.
+   * @param \Drupal\Core\Entity\EntityStorageInterface $state_storage
+   *   Moderation state entity storage.
+   * @param \Drupal\user\RoleStorageInterface $role_storage
+   *   The role storage.
+   */
+  public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $transition_storage, EntityStorageInterface $state_storage, RoleStorageInterface $role_storage) {
+    parent::__construct($entity_type, $transition_storage);
+    $this->stateStorage = $state_storage;
+    $this->roleStorage = $role_storage;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'content_moderation_transition_list';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildHeader() {
+    $header['to'] = $this->t('To state');
+    $header['label'] = $this->t('Button label');
+    $header['roles'] = $this->t('Allowed roles');
+
+    return $header + parent::buildHeader();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRow(EntityInterface $entity) {
+    $row['to']['#markup'] = $this->stateStorage->load($entity->getToState())->label();
+    $row['label'] = $entity->label();
+    $row['roles']['#markup'] = implode(', ', user_role_names(FALSE, 'use ' . $entity->id() . ' transition'));
+
+    return $row + parent::buildRow($entity);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function render() {
+    $build = parent::render();
+
+    $build['item'] = [
+      '#type' => 'item',
+      '#markup' => $this->t('On this screen you can define <em>transitions</em>. Every time an entity is saved, it undergoes a transition. It is not possible to save an entity if it tries do a transition not defined here. Transitions do not necessarily mean a state change, it is possible to transition from a state to the same state but that transition needs to be defined here as well.'),
+      '#weight' => -5,
+    ];
+
+    return $build;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $this->entities = $this->load();
+
+    // Get all the moderation states and sort them by weight.
+    $states = $this->stateStorage->loadMultiple();
+    uasort($states, array($this->entityType->getClass(), 'sort'));
+
+    /** @var \Drupal\content_moderation\ModerationStateTransitionInterface $entity */
+    $groups = array_fill_keys(array_keys($states), []);
+    foreach ($this->entities as $entity) {
+      $groups[$entity->getFromState()][] = $entity;
+    }
+
+    foreach ($groups as $group_name => $entities) {
+      $form[$group_name] = [
+        '#type' => 'details',
+        '#title' => $this->t('From @state to...', ['@state' => $states[$group_name]->label()]),
+        // Make sure that the first group is always open.
+        '#open' => $group_name === array_keys($groups)[0],
+      ];
+
+      $form[$group_name][$this->entitiesKey] = array(
+        '#type' => 'table',
+        '#header' => $this->buildHeader(),
+        '#empty' => t('There is no @label yet.', array('@label' => $this->entityType->getLabel())),
+        '#tabledrag' => array(
+          array(
+            'action' => 'order',
+            'relationship' => 'sibling',
+            'group' => 'weight',
+          ),
+        ),
+      );
+
+      $delta = 10;
+      // Change the delta of the weight field if have more than 20 entities.
+      if (!empty($this->weightKey)) {
+        $count = count($this->entities);
+        if ($count > 20) {
+          $delta = ceil($count / 2);
+        }
+      }
+      foreach ($entities as $entity) {
+        $row = $this->buildRow($entity);
+        if (isset($row['label'])) {
+          $row['label'] = array('#markup' => $row['label']);
+        }
+        if (isset($row['weight'])) {
+          $row['weight']['#delta'] = $delta;
+        }
+        $form[$group_name][$this->entitiesKey][$entity->id()] = $row;
+      }
+    }
+
+    $form['actions']['#type'] = 'actions';
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save order'),
+      '#button_type' => 'primary',
+    );
+
+    return $form;
+  }
+
+}
diff --git a/core/modules/content_moderation/src/Permissions.php b/core/modules/content_moderation/src/Permissions.php
index 201239f..027684c 100644
--- a/core/modules/content_moderation/src/Permissions.php
+++ b/core/modules/content_moderation/src/Permissions.php
@@ -3,7 +3,8 @@
 namespace Drupal\content_moderation;
 
 use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\workflows\Entity\Workflow;
+use Drupal\content_moderation\Entity\ModerationState;
+use Drupal\content_moderation\Entity\ModerationStateTransition;
 
 /**
  * Defines a class for dynamic permissions based on transitions.
@@ -21,17 +22,19 @@ class Permissions {
   public function transitionPermissions() {
     // @todo https://www.drupal.org/node/2779933 write a test for this.
     $perms = [];
-
-    /** @var \Drupal\workflows\WorkflowInterface $workflow */
-    foreach (Workflow::loadMultipleByType('content_moderation') as $id => $workflow) {
-      foreach ($workflow->getTransitions() as $transition) {
-        $perms['use ' . $workflow->id() . ' transition ' . $transition->id()] = [
-          'title' => $this->t('Use %transition transition from %workflow workflow.', [
-            '%transition' => $transition->label(),
-            '%workflow' => $workflow->label(),
-          ]),
-        ];
-      }
+    /* @var \Drupal\content_moderation\ModerationStateInterface[] $states */
+    $states = ModerationState::loadMultiple();
+    /* @var \Drupal\content_moderation\ModerationStateTransitionInterface $transition */
+    foreach (ModerationStateTransition::loadMultiple() as $id => $transition) {
+      $perms['use ' . $id . ' transition'] = [
+        'title' => $this->t('Use the %transition_name transition', [
+          '%transition_name' => $transition->label(),
+        ]),
+        'description' => $this->t('Move content from %from state to %to state.', [
+          '%from' => $states[$transition->getFromState()]->label(),
+          '%to' => $states[$transition->getToState()]->label(),
+        ]),
+      ];
     }
 
     return $perms;
diff --git a/core/modules/content_moderation/src/Plugin/Field/FieldFormatter/ContentModerationStateFormatter.php b/core/modules/content_moderation/src/Plugin/Field/FieldFormatter/ContentModerationStateFormatter.php
deleted file mode 100644
index 4e9da16..0000000
--- a/core/modules/content_moderation/src/Plugin/Field/FieldFormatter/ContentModerationStateFormatter.php
+++ /dev/null
@@ -1,48 +0,0 @@
-<?php
-
-namespace Drupal\content_moderation\Plugin\Field\FieldFormatter;
-
-use Drupal\Core\Field\FieldDefinitionInterface;
-use Drupal\Core\Field\FormatterBase;
-use Drupal\Core\Field\FieldItemListInterface;
-
-/**
- * Plugin implementation of the 'content_moderation_state' formatter.
- *
- * @FieldFormatter(
- *   id = "content_moderation_state",
- *   label = @Translation("Content moderation state"),
- *   field_types = {
- *     "string",
- *   }
- * )
- */
-class ContentModerationStateFormatter extends FormatterBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function viewElements(FieldItemListInterface $items, $langcode) {
-    $elements = array();
-
-    $entity = $items->getEntity();
-    $workflow = $entity->workflow->entity;
-    foreach ($items as $delta => $item) {
-      if (!$item->isEmpty()) {
-        $elements[$delta] = [
-          '#markup' => $workflow->getState($item->value)->label(),
-        ];
-      }
-    }
-
-    return $elements;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function isApplicable(FieldDefinitionInterface $field_definition) {
-    return $field_definition->getName() === 'moderation_state';
-  }
-
-}
diff --git a/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php b/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php
index 8c17d82..d6dc89d 100644
--- a/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php
+++ b/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php
@@ -3,7 +3,9 @@
 namespace Drupal\content_moderation\Plugin\Field\FieldWidget;
 
 use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Field\Plugin\Field\FieldWidget\OptionsSelectWidget;
@@ -21,7 +23,7 @@
  *   id = "moderation_state_default",
  *   label = @Translation("Moderation state"),
  *   field_types = {
- *     "string"
+ *     "entity_reference"
  *   }
  * )
  */
@@ -35,6 +37,20 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact
   protected $currentUser;
 
   /**
+   * Moderation state transition entity query.
+   *
+   * @var \Drupal\Core\Entity\Query\QueryInterface
+   */
+  protected $moderationStateTransitionEntityQuery;
+
+  /**
+   * Moderation state storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $moderationStateStorage;
+
+  /**
    * Moderation information service.
    *
    * @var \Drupal\content_moderation\ModerationInformation
@@ -49,6 +65,13 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact
   protected $entityTypeManager;
 
   /**
+   * Moderation state transition storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $moderationStateTransitionStorage;
+
+  /**
    * Moderation state transition validation service.
    *
    * @var \Drupal\content_moderation\StateTransitionValidation
@@ -72,13 +95,22 @@ class ModerationStateWidget extends OptionsSelectWidget implements ContainerFact
    *   Current user service.
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    *   Entity type manager.
+   * @param \Drupal\Core\Entity\EntityStorageInterface $moderation_state_storage
+   *   Moderation state storage.
+   * @param \Drupal\Core\Entity\EntityStorageInterface $moderation_state_transition_storage
+   *   Moderation state transition storage.
+   * @param \Drupal\Core\Entity\Query\QueryInterface $entity_query
+   *   Moderation transition entity query service.
    * @param \Drupal\content_moderation\ModerationInformation $moderation_information
    *   Moderation information service.
    * @param \Drupal\content_moderation\StateTransitionValidation $validator
    *   Moderation state transition validation service
    */
-  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, ModerationInformation $moderation_information, StateTransitionValidation $validator) {
+  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, EntityStorageInterface $moderation_state_storage, EntityStorageInterface $moderation_state_transition_storage, QueryInterface $entity_query, ModerationInformation $moderation_information, StateTransitionValidation $validator) {
     parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
+    $this->moderationStateTransitionEntityQuery = $entity_query;
+    $this->moderationStateTransitionStorage = $moderation_state_transition_storage;
+    $this->moderationStateStorage = $moderation_state_storage;
     $this->entityTypeManager = $entity_type_manager;
     $this->currentUser = $current_user;
     $this->moderationInformation = $moderation_information;
@@ -97,6 +129,9 @@ public static function create(ContainerInterface $container, array $configuratio
       $configuration['third_party_settings'],
       $container->get('current_user'),
       $container->get('entity_type.manager'),
+      $container->get('entity_type.manager')->getStorage('moderation_state'),
+      $container->get('entity_type.manager')->getStorage('moderation_state_transition'),
+      $container->get('entity.query')->get('moderation_state_transition', 'AND'),
       $container->get('content_moderation.moderation_information'),
       $container->get('content_moderation.state_transition_validation')
     );
@@ -116,18 +151,19 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       return $element + ['#access' => FALSE];
     }
 
-    $workflow = $this->moderationInformation->getWorkFlowForEntity($entity);
-    $default = $items->get($delta)->value ? $workflow->getState($items->get($delta)->value) : $workflow->getInitialState();
-    if (!$default) {
+    $default = $items->get($delta)->value ?: $bundle_entity->getThirdPartySetting('content_moderation', 'default_moderation_state', FALSE);
+    /** @var \Drupal\content_moderation\ModerationStateInterface $default_state */
+    $default_state = $this->entityTypeManager->getStorage('moderation_state')->load($default);
+    if (!$default || !$default_state) {
       throw new \UnexpectedValueException(sprintf('The %s bundle has an invalid moderation state configuration, moderation states are enabled but no default is set.', $bundle_entity->label()));
     }
 
-    /** @var \Drupal\workflows\Transition[] $transitions */
     $transitions = $this->validator->getValidTransitions($entity, $this->currentUser);
 
     $target_states = [];
+    /** @var \Drupal\content_moderation\Entity\ModerationStateTransition $transition */
     foreach ($transitions as $transition) {
-      $target_states[$transition->to()->id()] = $transition->label();
+      $target_states[$transition->getToState()] = $transition->label();
     }
 
     // @todo https://www.drupal.org/node/2779933 write a test for this.
@@ -135,8 +171,8 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       '#access' => FALSE,
       '#type' => 'select',
       '#options' => $target_states,
-      '#default_value' => $default->id(),
-      '#published' => $default->isPublishedState(),
+      '#default_value' => $default,
+      '#published' => $default ? $default_state->isPublishedState() : FALSE,
       '#key_column' => $this->column,
     ];
     $element['#element_validate'][] = array(get_class($this), 'validateElement');
@@ -161,7 +197,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
   public static function updateStatus($entity_type_id, ContentEntityInterface $entity, array $form, FormStateInterface $form_state) {
     $element = $form_state->getTriggeringElement();
     if (isset($element['#moderation_state'])) {
-      $entity->moderation_state->value = $element['#moderation_state'];
+      $entity->moderation_state->target_id = $element['#moderation_state'];
     }
   }
 
@@ -213,7 +249,7 @@ public static function processActions($element, FormStateInterface $form_state,
    * {@inheritdoc}
    */
   public static function isApplicable(FieldDefinitionInterface $field_definition) {
-    return $field_definition->getName() === 'moderation_state';
+    return parent::isApplicable($field_definition) && $field_definition->getName() === 'moderation_state';
   }
 
 }
diff --git a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
index 036f346..c32521c 100644
--- a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
+++ b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
@@ -2,7 +2,8 @@
 
 namespace Drupal\content_moderation\Plugin\Field;
 
-use Drupal\Core\Field\FieldItemList;
+use Drupal\content_moderation\Entity\ModerationState;
+use Drupal\Core\Field\EntityReferenceFieldItemList;
 
 /**
  * A computed field that provides a content entity's moderation state.
@@ -10,20 +11,19 @@
  * It links content entities to a moderation state configuration entity via a
  * moderation state content entity.
  */
-class ModerationStateFieldItemList extends FieldItemList {
+class ModerationStateFieldItemList extends EntityReferenceFieldItemList {
 
   /**
    * Gets the moderation state entity linked to a content entity revision.
    *
-   * @return string|null
-   *   The moderation state linked to a content entity revision.
+   * @return \Drupal\content_moderation\ModerationStateInterface|null
+   *   The moderation state configuration entity linked to a content entity
+   *   revision.
    */
   protected function getModerationState() {
     $entity = $this->getEntity();
 
-    /** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_info */
-    $moderation_info = \Drupal::service('content_moderation.moderation_information');
-    if (!$moderation_info->shouldModerateEntitiesOfBundle($entity->getEntityType(), $entity->bundle())) {
+    if (!\Drupal::service('content_moderation.moderation_information')->shouldModerateEntitiesOfBundle($entity->getEntityType(), $entity->bundle())) {
       return NULL;
     }
 
@@ -32,7 +32,6 @@ protected function getModerationState() {
         ->condition('content_entity_type_id', $entity->getEntityTypeId())
         ->condition('content_entity_id', $entity->id())
         ->condition('content_entity_revision_id', $entity->getRevisionId())
-        ->condition('workflow', $moderation_info->getWorkFlowForEntity($entity)->id())
         ->allRevisions()
         ->sort('revision_id', 'DESC')
         ->execute();
@@ -54,15 +53,17 @@ protected function getModerationState() {
           }
         }
 
-        return $content_moderation_state->get('moderation_state')->value;
+        return $content_moderation_state->get('moderation_state')->entity;
       }
     }
     // It is possible that the bundle does not exist at this point. For example,
     // the node type form creates a fake Node entity to get default values.
     // @see \Drupal\node\NodeTypeForm::form()
-    $workflow = $moderation_info->getWorkFlowForEntity($entity);
-    if ($workflow) {
-      return $workflow->getInitialState()->id();
+    $bundle_entity = \Drupal::entityTypeManager()
+      ->getStorage($entity->getEntityType()->getBundleEntityType())
+      ->load($entity->bundle());
+    if ($bundle_entity && ($default = $bundle_entity->getThirdPartySetting('content_moderation', 'default_moderation_state'))) {
+      return ModerationState::load($default);
     }
   }
 
@@ -92,11 +93,10 @@ protected function computeModerationFieldItemList() {
     // Compute the value of the moderation state.
     $index = 0;
     if (!isset($this->list[$index]) || $this->list[$index]->isEmpty()) {
-
       $moderation_state = $this->getModerationState();
       // Do not store NULL values in the static cache.
       if ($moderation_state) {
-        $this->list[$index] = $this->createItem($index, $moderation_state);
+        $this->list[$index] = $this->createItem($index, ['entity' => $moderation_state]);
       }
     }
   }
diff --git a/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php b/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php
index d90e19e..ca75604 100644
--- a/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php
+++ b/core/modules/content_moderation/src/Plugin/Validation/Constraint/ModerationStateConstraintValidator.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\content_moderation\Plugin\Validation\Constraint;
 
+use Drupal\content_moderation\Entity\ModerationState as ModerationStateEntity;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -92,13 +93,21 @@ public function validate($value, Constraint $constraint) {
       $original_entity = $original_entity->getTranslation($entity->language()->getId());
     }
 
-    $workflow = $this->moderationInformation->getWorkFlowForEntity($entity);
-    $new_state = $workflow->getState($entity->moderation_state->value) ?: $workflow->getInitialState();
-    $original_state = $workflow->getState($original_entity->moderation_state->value);
-    // @todo - what if $new_state references something that does not exist or
+    if ($entity->moderation_state->target_id) {
+      $new_state_id = $entity->moderation_state->target_id;
+    }
+    else {
+      $new_state_id = $default = $this->entityTypeManager
+        ->getStorage($entity->getEntityType()->getBundleEntityType())->load($entity->bundle())
+        ->getThirdPartySetting('content_moderation', 'default_moderation_state');
+    }
+    if ($new_state_id) {
+      $new_state = ModerationStateEntity::load($new_state_id);
+    }
+    // @todo - what if $new_state_id references something that does not exist or
     //   is null.
-    if (!$original_state->canTransitionTo($new_state->id())) {
-      $this->context->addViolation($constraint->message, ['%from' => $original_state->label(), '%to' => $new_state->label()]);
+    if (!$this->validation->isTransitionAllowed($original_entity->moderation_state->entity, $new_state)) {
+      $this->context->addViolation($constraint->message, ['%from' => $original_entity->moderation_state->entity->label(), '%to' => $new_state->label()]);
     }
   }
 
@@ -117,9 +126,9 @@ public function validate($value, Constraint $constraint) {
   protected function isFirstTimeModeration(EntityInterface $entity) {
     $original_entity = $this->moderationInformation->getLatestRevision($entity->getEntityTypeId(), $entity->id());
 
-    $original_id = $original_entity->moderation_state;
+    $original_id = $original_entity->moderation_state->target_id;
 
-    return !($entity->moderation_state && $original_entity && $original_id);
+    return !($entity->moderation_state->target_id && $original_entity && $original_id);
   }
 
 }
diff --git a/core/modules/content_moderation/src/Plugin/WorkflowType/ContentModeration.php b/core/modules/content_moderation/src/Plugin/WorkflowType/ContentModeration.php
deleted file mode 100644
index e61f25e..0000000
--- a/core/modules/content_moderation/src/Plugin/WorkflowType/ContentModeration.php
+++ /dev/null
@@ -1,166 +0,0 @@
-<?php
-
-namespace Drupal\content_moderation\Plugin\WorkflowType;
-
-use Drupal\Core\Access\AccessResult;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Session\AccountInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\content_moderation\ContentModerationState;
-use Drupal\workflows\Plugin\WorkflowTypeBase;
-use Drupal\workflows\StateInterface;
-use Drupal\workflows\WorkflowInterface;
-
-/**
- * Attaches workflows to content entity types and their bundles.
- *
- * @WorkflowType(
- *   id = "content_moderation",
- *   label = @Translation("Content moderation"),
- * )
- */
-class ContentModeration extends WorkflowTypeBase {
-
-  use StringTranslationTrait;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function checkWorkflowAccess(WorkflowInterface $entity, $operation, AccountInterface $account) {
-    if ($operation === 'view') {
-      return AccessResult::allowedIfHasPermission($account, 'view content moderation');
-    }
-    return parent::checkWorkflowAccess($entity, $operation, $account);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function decorateState(StateInterface $state) {
-    if (isset($this->configuration['states'][$state->id()])) {
-      $state = new ContentModerationState($state, $this->configuration['states'][$state->id()]['published'], $this->configuration['states'][$state->id()]['default_revision']);
-    }
-    else {
-      $state = new ContentModerationState($state);
-    }
-    return $state;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildStateConfigurationForm(FormStateInterface $form_state, WorkflowInterface $workflow, StateInterface $state = NULL) {
-    /** @var \Drupal\content_moderation\ContentModerationState $state */
-    $form = [];
-    $form['published'] = [
-      '#type' => 'checkbox',
-      '#title' => $this->t('Published'),
-      '#description' => $this->t('When content reaches this state it should be published.'),
-      '#default_value' => isset($state) ? $state->isPublishedState() : FALSE,
-    ];
-
-    $form['default_revision'] = [
-      '#type' => 'checkbox',
-      '#title' => $this->t('Default revision'),
-      '#description' => $this->t('When content reaches this state it should be made the default revision; this is implied for published states.'),
-      '#default_value' => isset($state) ? $state->isDefaultRevisionState() : FALSE,
-      // @todo Add form #state to force "make default" on when "published" is
-      // on for a state.
-      // @see https://www.drupal.org/node/2645614
-    ];
-    return $form;
-  }
-
-  /**
-   * Gets the entity types the workflow is applied to.
-   *
-   * @return string[]
-   *   The entity types the workflow is applied to.
-   */
-  public function getEntityTypes() {
-    return array_keys($this->configuration['entity_types']);
-  }
-
-  /**
-   * Gets the bundles of the entity type the workflow is applied to.
-   *
-   * @param string $entity_type_id
-   *   The entity type ID to get the bundles for.
-   *
-   * @return string[]
-   *   The bundles of the entity type the workflow is applied to.
-   */
-  public function getBundlesForEntityType($entity_type_id) {
-    return $this->configuration['entity_types'][$entity_type_id];
-  }
-
-  /**
-   * Checks if the workflow applies to the supplied entity type and bundle.
-   *
-   * @return bool
-   *   TRUE if the workflow applies to the supplied entity type and bundle.
-   *   FALSE if not.
-   */
-  public function appliesToEntityTypeAndBundle($entity_type_id, $bundle_id) {
-    if (isset($this->configuration['entity_types'][$entity_type_id])) {
-      return in_array($bundle_id, $this->configuration['entity_types'][$entity_type_id], TRUE);
-    }
-    return FALSE;
-  }
-
-  /**
-   * Removes an entity type ID / bundle ID from the workflow.
-   *
-   * @param string $entity_type_id
-   *   The entity type ID to remove.
-   * @param string $bundle_id
-   *   The bundle ID to remove.
-   */
-  public function removeEntityTypeAndBundle($entity_type_id, $bundle_id) {
-    $key = array_search($bundle_id, $this->configuration['entity_types'][$entity_type_id], TRUE);
-    if ($key !== FALSE) {
-      unset($this->configuration['entity_types'][$entity_type_id][$key]);
-      if (empty($this->configuration['entity_types'][$entity_type_id])) {
-        unset($this->configuration['entity_types'][$entity_type_id]);
-      }
-      else {
-        $this->configuration['entity_types'][$entity_type_id] = array_values($this->configuration['entity_types'][$entity_type_id]);
-      }
-    }
-  }
-
-  /**
-   * Add an entity type ID / bundle ID to the workflow.
-   *
-   * @param string $entity_type_id
-   *   The entity type ID to add.
-   * @param string $bundle_id
-   *   The bundle ID to add.
-   */
-  public function addEntityTypeAndBundle($entity_type_id, $bundle_id) {
-    if (!$this->appliesToEntityTypeAndBundle($entity_type_id, $bundle_id)) {
-      $this->configuration['entity_types'][$entity_type_id][] = $bundle_id;
-      natsort($this->configuration['entity_types'][$entity_type_id]);
-    }
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public function defaultConfiguration() {
-    // This plugin does not store anything per transition.
-    return [
-      'states' => [],
-      'entity_types' => [],
-    ];
-  }
-
-  /**
-   * @inheritDoc
-   */
-  public function calculateDependencies() {
-    // @todo : Implement calculateDependencies() method.
-    return [];
-  }
-
-}
diff --git a/core/modules/content_moderation/src/Routing/EntityTypeModerationRouteProvider.php b/core/modules/content_moderation/src/Routing/EntityTypeModerationRouteProvider.php
index d1dcd2b..c722a67 100644
--- a/core/modules/content_moderation/src/Routing/EntityTypeModerationRouteProvider.php
+++ b/core/modules/content_moderation/src/Routing/EntityTypeModerationRouteProvider.php
@@ -47,7 +47,7 @@ protected function getModerationFormRoute(EntityTypeInterface $entity_type) {
           '_entity_form' => "{$entity_type_id}.moderation",
           '_title' => 'Moderation',
         ])
-        ->setRequirement('_permission', 'administer content moderation')
+        ->setRequirement('_permission', 'administer moderation states')
         ->setOption('parameters', [
           $entity_type_id => ['type' => 'entity:' . $entity_type_id],
         ]);
diff --git a/core/modules/content_moderation/src/StateTransitionValidation.php b/core/modules/content_moderation/src/StateTransitionValidation.php
index e3d3376..2e2a4e2 100644
--- a/core/modules/content_moderation/src/StateTransitionValidation.php
+++ b/core/modules/content_moderation/src/StateTransitionValidation.php
@@ -3,8 +3,10 @@
 namespace Drupal\content_moderation;
 
 use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Core\Session\AccountInterface;
-use Drupal\workflows\Transition;
+use Drupal\content_moderation\Entity\ModerationStateTransition;
 
 /**
  * Validates whether a certain state transition is allowed.
@@ -12,11 +14,18 @@
 class StateTransitionValidation implements StateTransitionValidationInterface {
 
   /**
-   * The moderation information service.
+   * Entity type manager.
    *
-   * @var \Drupal\content_moderation\ModerationInformationInterface
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
    */
-  protected $moderationInfo;
+  protected $entityTypeManager;
+
+  /**
+   * Entity query factory.
+   *
+   * @var \Drupal\Core\Entity\Query\QueryFactory
+   */
+  protected $queryFactory;
 
   /**
    * Stores the possible state transitions.
@@ -28,23 +37,211 @@ class StateTransitionValidation implements StateTransitionValidationInterface {
   /**
    * Constructs a new StateTransitionValidation.
    *
-   * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
-   *   The moderation information service.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager service.
+   * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
+   *   The entity query factory.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, QueryFactory $query_factory) {
+    $this->entityTypeManager = $entity_type_manager;
+    $this->queryFactory = $query_factory;
+  }
+
+  /**
+   * Computes a mapping of possible transitions.
+   *
+   * This method is uncached and will recalculate the list on every request.
+   * In most cases you want to use getPossibleTransitions() instead.
+   *
+   * @see static::getPossibleTransitions()
+   *
+   * @return array[]
+   *   An array containing all possible transitions. Each entry is keyed by the
+   *   "from" state, and the value is an array of all legal "to" states based
+   *   on the currently defined transition objects.
+   */
+  protected function calculatePossibleTransitions() {
+    $transitions = $this->transitionStorage()->loadMultiple();
+
+    $possible_transitions = [];
+    /** @var \Drupal\content_moderation\ModerationStateTransitionInterface $transition */
+    foreach ($transitions as $transition) {
+      $possible_transitions[$transition->getFromState()][] = $transition->getToState();
+    }
+    return $possible_transitions;
+  }
+
+  /**
+   * Returns a mapping of possible transitions.
+   *
+   * @return array[]
+   *   An array containing all possible transitions. Each entry is keyed by the
+   *   "from" state, and the value is an array of all legal "to" states based
+   *   on the currently defined transition objects.
    */
-  public function __construct(ModerationInformationInterface $moderation_info) {
-    $this->moderationInfo = $moderation_info;
+  protected function getPossibleTransitions() {
+    if (empty($this->possibleTransitions)) {
+      $this->possibleTransitions = $this->calculatePossibleTransitions();
+    }
+    return $this->possibleTransitions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getValidTransitionTargets(ContentEntityInterface $entity, AccountInterface $user) {
+    $bundle = $this->loadBundleEntity($entity->getEntityType()->getBundleEntityType(), $entity->bundle());
+
+    $states_for_bundle = $bundle->getThirdPartySetting('content_moderation', 'allowed_moderation_states', []);
+
+    /** @var \Drupal\content_moderation\Entity\ModerationState $current_state */
+    $current_state = $entity->moderation_state->entity;
+
+    $all_transitions = $this->getPossibleTransitions();
+    $destination_ids = $all_transitions[$current_state->id()];
+
+    $destination_ids = array_intersect($states_for_bundle, $destination_ids);
+    $destinations = $this->entityTypeManager->getStorage('moderation_state')->loadMultiple($destination_ids);
+
+    return array_filter($destinations, function(ModerationStateInterface $destination_state) use ($current_state, $user) {
+      return $this->userMayTransition($current_state, $destination_state, $user);
+    });
   }
 
   /**
    * {@inheritdoc}
    */
   public function getValidTransitions(ContentEntityInterface $entity, AccountInterface $user) {
-    $workflow = $this->moderationInfo->getWorkFlowForEntity($entity);
-    $current_state = $entity->moderation_state->value ? $workflow->getState($entity->moderation_state->value) : $workflow->getInitialState();
+    $bundle = $this->loadBundleEntity($entity->getEntityType()->getBundleEntityType(), $entity->bundle());
+
+    /** @var \Drupal\content_moderation\Entity\ModerationState $current_state */
+    $current_state = $entity->moderation_state->entity;
+    $current_state_id = $current_state ? $current_state->id() : $bundle->getThirdPartySetting('content_moderation', 'default_moderation_state');
+
+    // Determine the states that are legal on this bundle.
+    $legal_bundle_states = $bundle->getThirdPartySetting('content_moderation', 'allowed_moderation_states', []);
 
-    return array_filter($current_state->getTransitions(), function(Transition $transition) use ($workflow, $user) {
-      return $user->hasPermission('use ' . $workflow->id() . ' transition ' . $transition->id());
+    // Legal transitions include those that are possible from the current state,
+    // filtered by those whose target is legal on this bundle and that the
+    // user has access to execute.
+    $transitions = array_filter($this->getTransitionsFrom($current_state_id), function(ModerationStateTransition $transition) use ($legal_bundle_states, $user) {
+      return in_array($transition->getToState(), $legal_bundle_states, TRUE)
+        && $user->hasPermission('use ' . $transition->id() . ' transition');
     });
+
+    return $transitions;
+  }
+
+  /**
+   * Returns a list of possible transitions from a given state.
+   *
+   * This list is based only on those transitions that exist, not what
+   * transitions are legal in a given context.
+   *
+   * @param string $state_name
+   *   The machine name of the state from which we are transitioning.
+   *
+   * @return ModerationStateTransition[]
+   *   A list of possible transitions from a given state.
+   */
+  protected function getTransitionsFrom($state_name) {
+    $result = $this->transitionStateQuery()
+      ->condition('stateFrom', $state_name)
+      ->sort('weight')
+      ->execute();
+
+    return $this->transitionStorage()->loadMultiple($result);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function userMayTransition(ModerationStateInterface $from, ModerationStateInterface $to, AccountInterface $user) {
+    if ($transition = $this->getTransitionFromStates($from, $to)) {
+      return $user->hasPermission('use ' . $transition->id() . ' transition');
+    }
+    return FALSE;
+  }
+
+  /**
+   * Returns the transition object that transitions from one state to another.
+   *
+   * @param \Drupal\content_moderation\ModerationStateInterface $from
+   *   The origin state.
+   * @param \Drupal\content_moderation\ModerationStateInterface $to
+   *   The destination state.
+   *
+   * @return ModerationStateTransition|null
+   *   A transition object, or NULL if there is no such transition.
+   */
+  protected function getTransitionFromStates(ModerationStateInterface $from, ModerationStateInterface $to) {
+    $from = $this->transitionStateQuery()
+      ->condition('stateFrom', $from->id())
+      ->condition('stateTo', $to->id())
+      ->execute();
+
+    $transitions = $this->transitionStorage()->loadMultiple($from);
+
+    if ($transitions) {
+      return current($transitions);
+    }
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isTransitionAllowed(ModerationStateInterface $from, ModerationStateInterface $to) {
+    $allowed_transitions = $this->calculatePossibleTransitions();
+    if (isset($allowed_transitions[$from->id()])) {
+      return in_array($to->id(), $allowed_transitions[$from->id()], TRUE);
+    }
+    return FALSE;
+  }
+
+  /**
+   * Returns a transition state entity query.
+   *
+   * @return \Drupal\Core\Entity\Query\QueryInterface
+   *   A transition state entity query.
+   */
+  protected function transitionStateQuery() {
+    return $this->queryFactory->get('moderation_state_transition', 'AND');
+  }
+
+  /**
+   * Returns the transition entity storage service.
+   *
+   * @return \Drupal\Core\Entity\EntityStorageInterface
+   *   The transition state entity storage.
+   */
+  protected function transitionStorage() {
+    return $this->entityTypeManager->getStorage('moderation_state_transition');
+  }
+
+  /**
+   * Returns the state entity storage service.
+   *
+   * @return \Drupal\Core\Entity\EntityStorageInterface
+   *   The moderation state entity storage.
+   */
+  protected function stateStorage() {
+    return $this->entityTypeManager->getStorage('moderation_state');
+  }
+
+  /**
+   * Loads a specific bundle entity.
+   *
+   * @param string $bundle_entity_type_id
+   *   The bundle entity type ID.
+   * @param string $bundle_id
+   *   The bundle ID.
+   *
+   * @return \Drupal\Core\Config\Entity\ConfigEntityInterface|null
+   *   The specific bundle entity.
+   */
+  protected function loadBundleEntity($bundle_entity_type_id, $bundle_id) {
+    return $this->entityTypeManager->getStorage($bundle_entity_type_id)->load($bundle_id);
   }
 
 }
diff --git a/core/modules/content_moderation/src/StateTransitionValidationInterface.php b/core/modules/content_moderation/src/StateTransitionValidationInterface.php
index 1acbf05..5ef0dd1 100644
--- a/core/modules/content_moderation/src/StateTransitionValidationInterface.php
+++ b/core/modules/content_moderation/src/StateTransitionValidationInterface.php
@@ -11,6 +11,20 @@
 interface StateTransitionValidationInterface {
 
   /**
+   * Gets a list of states a user may transition an entity to.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity to be transitioned.
+   * @param \Drupal\Core\Session\AccountInterface $user
+   *   The account that wants to perform a transition.
+   *
+   * @return \Drupal\content_moderation\Entity\ModerationState[]
+   *   Returns an array of States to which the specified user may transition the
+   *   entity.
+   */
+  public function getValidTransitionTargets(ContentEntityInterface $entity, AccountInterface $user);
+
+  /**
    * Gets a list of transitions that are legal for this user on this entity.
    *
    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
@@ -18,9 +32,40 @@
    * @param \Drupal\Core\Session\AccountInterface $user
    *   The account that wants to perform a transition.
    *
-   * @return \Drupal\workflows\Transition[]
+   * @return \Drupal\content_moderation\Entity\ModerationStateTransition[]
    *   The list of transitions that are legal for this user on this entity.
    */
   public function getValidTransitions(ContentEntityInterface $entity, AccountInterface $user);
 
+  /**
+   * Determines if a user is allowed to transition from one state to another.
+   *
+   * This method will also return FALSE if there is no transition between the
+   * specified states at all.
+   *
+   * @param \Drupal\content_moderation\ModerationStateInterface $from
+   *   The origin state.
+   * @param \Drupal\content_moderation\ModerationStateInterface $to
+   *   The destination state.
+   * @param \Drupal\Core\Session\AccountInterface $user
+   *   The user to validate.
+   *
+   * @return bool
+   *   TRUE if the given user may transition between those two states.
+   */
+  public function userMayTransition(ModerationStateInterface $from, ModerationStateInterface $to, AccountInterface $user);
+
+  /**
+   * Determines a transition allowed.
+   *
+   * @param \Drupal\content_moderation\ModerationStateInterface $from
+   *   The origin state.
+   * @param \Drupal\content_moderation\ModerationStateInterface $to
+   *   The destination state.
+   *
+   * @return bool
+   *   Is the transition allowed.
+   */
+  public function isTransitionAllowed(ModerationStateInterface $from, ModerationStateInterface $to);
+
 }
diff --git a/core/modules/content_moderation/src/Tests/ModerationFormTest.php b/core/modules/content_moderation/src/Tests/ModerationFormTest.php
index 16da4e4..d6c92b9 100644
--- a/core/modules/content_moderation/src/Tests/ModerationFormTest.php
+++ b/core/modules/content_moderation/src/Tests/ModerationFormTest.php
@@ -15,7 +15,10 @@ class ModerationFormTest extends ModerationStateTestBase {
   protected function setUp() {
     parent::setUp();
     $this->drupalLogin($this->adminUser);
-    $this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE);
+    $this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE, [
+      'draft',
+      'published',
+    ], 'draft');
     $this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
   }
 
diff --git a/core/modules/content_moderation/src/Tests/ModerationLocaleTest.php b/core/modules/content_moderation/src/Tests/ModerationLocaleTest.php
index a78c104..0d40356 100644
--- a/core/modules/content_moderation/src/Tests/ModerationLocaleTest.php
+++ b/core/modules/content_moderation/src/Tests/ModerationLocaleTest.php
@@ -28,7 +28,13 @@ public function testTranslateModeratedContent() {
     $this->drupalLogin($this->rootUser);
 
     // Enable moderation on Article node type.
-    $this->createContentTypeFromUi('Article', 'article', TRUE);
+    $this->createContentTypeFromUi(
+      'Article',
+      'article',
+      TRUE,
+      ['draft', 'published', 'archived'],
+      'draft'
+    );
 
     // Add French language.
     $edit = [
@@ -97,9 +103,9 @@ public function testTranslateModeratedContent() {
     $french_node = $english_node->getTranslation('fr');
     $this->assertEqual('French node', $french_node->label());
 
-    $this->assertEqual($english_node->moderation_state->value, 'published');
+    $this->assertEqual($english_node->moderation_state->target_id, 'published');
     $this->assertTrue($english_node->isPublished());
-    $this->assertEqual($french_node->moderation_state->value, 'draft');
+    $this->assertEqual($french_node->moderation_state->target_id, 'draft');
     $this->assertFalse($french_node->isPublished());
 
     // Create another article with its translation. This time we will publish
@@ -127,9 +133,9 @@ public function testTranslateModeratedContent() {
     $this->assertText(t('Article Translated node has been updated.'));
     $english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
     $french_node = $english_node->getTranslation('fr');
-    $this->assertEqual($french_node->moderation_state->value, 'published');
+    $this->assertEqual($french_node->moderation_state->target_id, 'published');
     $this->assertTrue($french_node->isPublished());
-    $this->assertEqual($english_node->moderation_state->value, 'draft');
+    $this->assertEqual($english_node->moderation_state->target_id, 'draft');
     $this->assertFalse($english_node->isPublished());
 
     // Now check that we can create a new draft of the translation.
@@ -140,7 +146,7 @@ public function testTranslateModeratedContent() {
     $this->assertText(t('Article New draft of translated node has been updated.'));
     $english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
     $french_node = $english_node->getTranslation('fr');
-    $this->assertEqual($french_node->moderation_state->value, 'published');
+    $this->assertEqual($french_node->moderation_state->target_id, 'published');
     $this->assertTrue($french_node->isPublished());
     $this->assertEqual($french_node->getTitle(), 'Translated node', 'The default revision of the published translation remains the same.');
 
@@ -152,7 +158,7 @@ public function testTranslateModeratedContent() {
     $this->assertText(t('The moderation state has been updated.'));
     $english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
     $french_node = $english_node->getTranslation('fr');
-    $this->assertEqual($french_node->moderation_state->value, 'published');
+    $this->assertEqual($french_node->moderation_state->target_id, 'published');
     $this->assertTrue($french_node->isPublished());
     $this->assertEqual($french_node->getTitle(), 'New draft of translated node', 'The draft has replaced the published revision.');
 
@@ -160,7 +166,7 @@ public function testTranslateModeratedContent() {
     $this->drupalPostForm('node/' . $english_node->id() . '/edit', [], t('Save and Publish (this translation)'));
     $this->assertText(t('Article Another node has been updated.'));
     $english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
-    $this->assertEqual($english_node->moderation_state->value, 'published');
+    $this->assertEqual($english_node->moderation_state->target_id, 'published');
 
     // Archive the node and its translation.
     $this->drupalPostForm('node/' . $english_node->id() . '/edit', [], t('Save and Archive (this translation)'));
@@ -169,9 +175,9 @@ public function testTranslateModeratedContent() {
     $this->assertText(t('Article New draft of translated node has been updated.'));
     $english_node = $this->drupalGetNodeByTitle('Another node', TRUE);
     $french_node = $english_node->getTranslation('fr');
-    $this->assertEqual($english_node->moderation_state->value, 'archived');
+    $this->assertEqual($english_node->moderation_state->target_id, 'archived');
     $this->assertFalse($english_node->isPublished());
-    $this->assertEqual($french_node->moderation_state->value, 'archived');
+    $this->assertEqual($french_node->moderation_state->target_id, 'archived');
     $this->assertFalse($french_node->isPublished());
 
     // Create another article with its translation. This time publishing english
diff --git a/core/modules/content_moderation/src/Tests/ModerationStateBlockTest.php b/core/modules/content_moderation/src/Tests/ModerationStateBlockTest.php
index e42f536..03a4b0e 100644
--- a/core/modules/content_moderation/src/Tests/ModerationStateBlockTest.php
+++ b/core/modules/content_moderation/src/Tests/ModerationStateBlockTest.php
@@ -59,7 +59,12 @@ public function testCustomBlockModeration() {
 
     // Enable moderation for custom blocks at
     // admin/structure/block/block-content/manage/basic/moderation.
-    $edit = ['workflow' => 'editorial'];
+    $edit = [
+      'enable_moderation_state' => TRUE,
+      'allowed_moderation_states_unpublished[draft]' => TRUE,
+      'allowed_moderation_states_published[published]' => TRUE,
+      'default_moderation_state' => 'draft',
+    ];
     $this->drupalPostForm(NULL, $edit, t('Save'));
     $this->assertText(t('Your settings have been saved.'));
 
diff --git a/core/modules/content_moderation/src/Tests/ModerationStateNodeTest.php b/core/modules/content_moderation/src/Tests/ModerationStateNodeTest.php
index a89ec9f..e2069b4 100644
--- a/core/modules/content_moderation/src/Tests/ModerationStateNodeTest.php
+++ b/core/modules/content_moderation/src/Tests/ModerationStateNodeTest.php
@@ -4,7 +4,6 @@
 
 use Drupal\Core\Url;
 use Drupal\node\Entity\Node;
-use Drupal\workflows\Entity\Workflow;
 
 /**
  * Tests general content moderation workflow for nodes.
@@ -19,7 +18,13 @@ class ModerationStateNodeTest extends ModerationStateTestBase {
   protected function setUp() {
     parent::setUp();
     $this->drupalLogin($this->adminUser);
-    $this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE);
+    $this->createContentTypeFromUi(
+      'Moderated content',
+      'moderated_content',
+      TRUE,
+      ['draft', 'needs_review', 'published'],
+      'draft'
+    );
     $this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
   }
 
@@ -30,11 +35,19 @@ public function testCreatingContent() {
     $this->drupalPostForm('node/add/moderated_content', [
       'title[0][value]' => 'moderated content',
     ], t('Save and Create New Draft'));
-    $node = $this->getNodeByTitle('moderated content');
-    if (!$node) {
+    $nodes = \Drupal::entityTypeManager()
+      ->getStorage('node')
+      ->loadByProperties([
+        'title' => 'moderated content',
+      ]);
+
+    if (!$nodes) {
       $this->fail('Test node was not saved correctly.');
+      return;
     }
-    $this->assertEqual('draft', $node->moderation_state->value);
+
+    $node = reset($nodes);
+    $this->assertEqual('draft', $node->moderation_state->target_id);
 
     $path = 'node/' . $node->id() . '/edit';
     // Set up published revision.
@@ -43,7 +56,7 @@ public function testCreatingContent() {
     /* @var \Drupal\node\NodeInterface $node */
     $node = \Drupal::entityTypeManager()->getStorage('node')->load($node->id());
     $this->assertTrue($node->isPublished());
-    $this->assertEqual('published', $node->moderation_state->value);
+    $this->assertEqual('published', $node->moderation_state->target_id);
 
     // Verify that the state field is not shown.
     $this->assertNoText('Published');
@@ -52,40 +65,30 @@ public function testCreatingContent() {
     $this->drupalPostForm('node/' . $node->id() . '/delete', array(), t('Delete'));
     $this->assertText(t('The Moderated content moderated content has been deleted.'));
 
-    // Disable content moderation.
-    $this->drupalPostForm('admin/structure/types/manage/moderated_content/moderation', ['workflow' => ''], t('Save'));
     $this->drupalGet('admin/structure/types/manage/moderated_content/moderation');
-    $this->assertOptionSelected('edit-workflow', '');
-    // Ensure the parent environment is up-to-date.
-    // @see content_moderation_workflow_insert()
-    \Drupal::service('entity_type.bundle.info')->clearCachedBundles();
-    \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
-
-    // Create a new node.
+    $this->assertFieldByName('enable_moderation_state');
+    $this->assertFieldChecked('edit-enable-moderation-state');
+    $this->drupalPostForm(NULL, ['enable_moderation_state' => FALSE], t('Save'));
+    $this->drupalGet('admin/structure/types/manage/moderated_content/moderation');
+    $this->assertFieldByName('enable_moderation_state');
+    $this->assertNoFieldChecked('edit-enable-moderation-state');
     $this->drupalPostForm('node/add/moderated_content', [
       'title[0][value]' => 'non-moderated content',
     ], t('Save and publish'));
 
-    $node = $this->getNodeByTitle('non-moderated content');
-    if (!$node) {
+    $nodes = \Drupal::entityTypeManager()
+      ->getStorage('node')
+      ->loadByProperties([
+        'title' => 'non-moderated content',
+      ]);
+
+    if (!$nodes) {
       $this->fail('Non-moderated test node was not saved correctly.');
+      return;
     }
-    $this->assertEqual(NULL, $node->moderation_state->value);
-
-    // \Drupal\content_moderation\Form\BundleModerationConfigurationForm()
-    // should not list workflows with no states.
-    $workflow = Workflow::create(['id' => 'stateless', 'label' => 'Stateless', 'type' => 'content_moderation']);
-    $workflow->save();
 
-    $this->drupalGet('admin/structure/types/manage/moderated_content/moderation');
-    $this->assertNoText('Stateless');
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
-      ->save();
-    $this->drupalGet('admin/structure/types/manage/moderated_content/moderation');
-    $this->assertText('Stateless');
+    $node = reset($nodes);
+    $this->assertEqual(NULL, $node->moderation_state->target_id);
   }
 
   /**
diff --git a/core/modules/content_moderation/src/Tests/ModerationStateNodeTypeTest.php b/core/modules/content_moderation/src/Tests/ModerationStateNodeTypeTest.php
index 39e1a2c..debb32c 100644
--- a/core/modules/content_moderation/src/Tests/ModerationStateNodeTypeTest.php
+++ b/core/modules/content_moderation/src/Tests/ModerationStateNodeTypeTest.php
@@ -42,16 +42,12 @@ public function testEnablingOnExistingContent() {
     ], t('Save and publish'));
     $this->assertText('Not moderated Test has been created.');
 
-    // Now enable moderation state, ensuring all the expected links and tabs are
-    // present.
-    $this->drupalGet('admin/structure/types');
-    $this->assertLinkByHref('admin/structure/types/manage/not_moderated/moderation');
-    $this->drupalGet('admin/structure/types/manage/not_moderated');
-    $this->assertLinkByHref('admin/structure/types/manage/not_moderated/moderation');
-    $this->drupalGet('admin/structure/types/manage/not_moderated/moderation');
-    $this->assertOptionSelected('edit-workflow', '');
-    $edit['workflow'] = 'editorial';
-    $this->drupalPostForm(NULL, $edit, t('Save'));
+    // Now enable moderation state.
+    $this->enableModerationThroughUi(
+      'not_moderated',
+      ['draft', 'needs_review', 'published'],
+      'draft'
+    );
 
     // And make sure it works.
     $nodes = \Drupal::entityTypeManager()->getStorage('node')
diff --git a/core/modules/content_moderation/src/Tests/ModerationStateStatesTest.php b/core/modules/content_moderation/src/Tests/ModerationStateStatesTest.php
new file mode 100644
index 0000000..3c5fd14
--- /dev/null
+++ b/core/modules/content_moderation/src/Tests/ModerationStateStatesTest.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Drupal\content_moderation\Tests;
+
+/**
+ * Tests moderation state config entity.
+ *
+ * @group content_moderation
+ */
+class ModerationStateStatesTest extends ModerationStateTestBase {
+
+  /**
+   * Tests route access/permissions.
+   */
+  public function testAccess() {
+    $paths = [
+      'admin/config/workflow/moderation',
+      'admin/config/workflow/moderation/states',
+      'admin/config/workflow/moderation/states/add',
+      'admin/config/workflow/moderation/states/draft',
+      'admin/config/workflow/moderation/states/draft/delete',
+    ];
+
+    foreach ($paths as $path) {
+      $this->drupalGet($path);
+      // No access.
+      $this->assertResponse(403);
+    }
+    $this->drupalLogin($this->adminUser);
+    foreach ($paths as $path) {
+      $this->drupalGet($path);
+      // User has access.
+      $this->assertResponse(200);
+    }
+  }
+
+  /**
+   * Tests administration of moderation state entity.
+   */
+  public function testStateAdministration() {
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet('admin/config/workflow/moderation');
+    $this->assertLink('Moderation states');
+    $this->assertLink('Moderation state transitions');
+    $this->clickLink('Moderation states');
+    $this->assertLink('Add moderation state');
+    $this->assertText('Draft');
+    // Edit the draft.
+    $this->clickLink('Edit', 0);
+    $this->assertFieldByName('label', 'Draft');
+    $this->assertNoFieldChecked('edit-published');
+    $this->drupalPostForm(NULL, [
+      'label' => 'Drafty',
+    ], t('Save'));
+    $this->assertText('Saved the Drafty Moderation state.');
+    $this->drupalGet('admin/config/workflow/moderation/states/draft');
+    $this->assertFieldByName('label', 'Drafty');
+    $this->drupalPostForm(NULL, [
+      'label' => 'Draft',
+    ], t('Save'));
+    $this->assertText('Saved the Draft Moderation state.');
+    $this->clickLink(t('Add moderation state'));
+    $this->drupalPostForm(NULL, [
+      'label' => 'Expired',
+      'id' => 'expired',
+    ], t('Save'));
+    $this->assertText('Created the Expired Moderation state.');
+    $this->drupalGet('admin/config/workflow/moderation/states/expired');
+    $this->clickLink('Delete');
+    $this->assertText('Are you sure you want to delete Expired?');
+    $this->drupalPostForm(NULL, [], t('Delete'));
+    $this->assertText('Moderation state Expired deleted');
+  }
+
+}
diff --git a/core/modules/content_moderation/src/Tests/ModerationStateTestBase.php b/core/modules/content_moderation/src/Tests/ModerationStateTestBase.php
index 22307df..ddd275e 100644
--- a/core/modules/content_moderation/src/Tests/ModerationStateTestBase.php
+++ b/core/modules/content_moderation/src/Tests/ModerationStateTestBase.php
@@ -5,6 +5,7 @@
 use Drupal\Core\Session\AccountInterface;
 use Drupal\simpletest\WebTestBase;
 use Drupal\user\Entity\Role;
+use Drupal\content_moderation\Entity\ModerationState;
 
 /**
  * Defines a base class for moderation state tests.
@@ -29,15 +30,18 @@
    * @var array
    */
   protected $permissions = [
-    'administer content moderation',
+    'administer moderation states',
+    'administer moderation state transitions',
+    'use draft_draft transition',
+    'use draft_published transition',
+    'use published_draft transition',
+    'use published_archived transition',
     'access administration pages',
     'administer content types',
     'administer nodes',
     'view latest version',
     'view any unpublished content',
     'access content overview',
-    'use editorial transition create_new_draft',
-    'use editorial transition publish',
   ];
 
   /**
@@ -64,21 +68,6 @@ protected function setUp() {
   }
 
   /**
-   * Gets the permission machine name for a transition.
-   *
-   * @param string $workflow_id
-   *   The workflow ID.
-   * @param string $transition_id
-   *   The transition ID.
-   *
-   * @return string
-   *   The permission machine name for a transition.
-   */
-  protected function getWorkflowTransitionPermission($workflow_id, $transition_id) {
-    return 'use ' . $workflow_id . ' transition ' . $transition_id;
-  }
-
-  /**
    * Creates a content-type from the UI.
    *
    * @param string $content_type_name
@@ -87,10 +76,12 @@ protected function getWorkflowTransitionPermission($workflow_id, $transition_id)
    *   Machine name.
    * @param bool $moderated
    *   TRUE if should be moderated.
-   * @param string $workflow_id
-   *   The workflow to attach to the bundle.
+   * @param string[] $allowed_states
+   *   Array of allowed state IDs.
+   * @param string $default_state
+   *   Default state.
    */
-  protected function createContentTypeFromUi($content_type_name, $content_type_id, $moderated = FALSE, $workflow_id = 'editorial') {
+  protected function createContentTypeFromUi($content_type_name, $content_type_id, $moderated = FALSE, array $allowed_states = [], $default_state = NULL) {
     $this->drupalGet('admin/structure/types');
     $this->clickLink('Add content type');
     $edit = [
@@ -100,7 +91,7 @@ protected function createContentTypeFromUi($content_type_name, $content_type_id,
     $this->drupalPostForm(NULL, $edit, t('Save content type'));
 
     if ($moderated) {
-      $this->enableModerationThroughUi($content_type_id, $workflow_id);
+      $this->enableModerationThroughUi($content_type_id, $allowed_states, $default_state);
     }
   }
 
@@ -109,16 +100,31 @@ protected function createContentTypeFromUi($content_type_name, $content_type_id,
    *
    * @param string $content_type_id
    *   Machine name.
-   * @param string $workflow_id
-   *   The workflow to attach to the bundle.
+   * @param string[] $allowed_states
+   *   Array of allowed state IDs.
+   * @param string $default_state
+   *   Default state.
    */
-  protected function enableModerationThroughUi($content_type_id, $workflow_id = 'editorial') {
-    $edit['workflow'] = $workflow_id;
-    $this->drupalPostForm('admin/structure/types/manage/' . $content_type_id . '/moderation', $edit, t('Save'));
-    // Ensure the parent environment is up-to-date.
-    // @see content_moderation_workflow_insert()
-    \Drupal::service('entity_type.bundle.info')->clearCachedBundles();
-    \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
+  protected function enableModerationThroughUi($content_type_id, array $allowed_states, $default_state) {
+    $this->drupalGet('admin/structure/types');
+    $this->assertLinkByHref('admin/structure/types/manage/' . $content_type_id . '/moderation');
+    $this->drupalGet('admin/structure/types/manage/' . $content_type_id);
+    $this->assertLinkByHref('admin/structure/types/manage/' . $content_type_id . '/moderation');
+    $this->drupalGet('admin/structure/types/manage/' . $content_type_id . '/moderation');
+    $this->assertFieldByName('enable_moderation_state');
+    $this->assertNoFieldChecked('edit-enable-moderation-state');
+
+    $edit['enable_moderation_state'] = 1;
+
+    /** @var ModerationState $state */
+    foreach (ModerationState::loadMultiple() as $state) {
+      $key = $state->isPublishedState() ? 'allowed_moderation_states_published[' . $state->id() . ']' : 'allowed_moderation_states_unpublished[' . $state->id() . ']';
+      $edit[$key] = in_array($state->id(), $allowed_states, TRUE) ? $state->id() : FALSE;
+    }
+
+    $edit['default_moderation_state'] = $default_state;
+
+    $this->drupalPostForm(NULL, $edit, t('Save'));
   }
 
   /**
diff --git a/core/modules/content_moderation/src/Tests/ModerationStateTransitionsTest.php b/core/modules/content_moderation/src/Tests/ModerationStateTransitionsTest.php
new file mode 100644
index 0000000..703561b
--- /dev/null
+++ b/core/modules/content_moderation/src/Tests/ModerationStateTransitionsTest.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Drupal\content_moderation\Tests;
+
+/**
+ * Tests moderation state transition config entity.
+ *
+ * @group content_moderation
+ */
+class ModerationStateTransitionsTest extends ModerationStateTestBase {
+
+  /**
+   * Tests route access/permissions.
+   */
+  public function testAccess() {
+    $paths = [
+      'admin/config/workflow/moderation/transitions',
+      'admin/config/workflow/moderation/transitions/add',
+      'admin/config/workflow/moderation/transitions/draft_published',
+      'admin/config/workflow/moderation/transitions/draft_published/delete',
+    ];
+
+    foreach ($paths as $path) {
+      $this->drupalGet($path);
+      // No access.
+      $this->assertResponse(403);
+    }
+    $this->drupalLogin($this->adminUser);
+    foreach ($paths as $path) {
+      $this->drupalGet($path);
+      // User has access.
+      $this->assertResponse(200);
+    }
+  }
+
+  /**
+   * Tests administration of moderation state transition entity.
+   */
+  public function testTransitionAdministration() {
+    $this->drupalLogin($this->adminUser);
+
+    $this->drupalGet('admin/config/workflow/moderation');
+    $this->clickLink('Moderation state transitions');
+    $this->assertLink('Add moderation state transition');
+    $this->assertText('Create New Draft');
+
+    // Edit the Draft » Draft review.
+    $this->drupalGet('admin/config/workflow/moderation/transitions/draft_draft');
+    $this->assertFieldByName('label', 'Create New Draft');
+    $this->assertFieldByName('stateFrom', 'draft');
+    $this->assertFieldByName('stateTo', 'draft');
+    $this->drupalPostForm(NULL, [
+      'label' => 'Create Draft',
+    ], t('Save'));
+    $this->assertText('Saved the Create Draft Moderation state transition.');
+    $this->drupalGet('admin/config/workflow/moderation/transitions/draft_draft');
+    $this->assertFieldByName('label', 'Create Draft');
+    // Now set it back.
+    $this->drupalPostForm(NULL, [
+      'label' => 'Create New Draft',
+    ], t('Save'));
+    $this->assertText('Saved the Create New Draft Moderation state transition.');
+
+    // Add a new state.
+    $this->drupalGet('admin/config/workflow/moderation/states/add');
+    $this->drupalPostForm(NULL, [
+      'label' => 'Expired',
+      'id' => 'expired',
+    ], t('Save'));
+    $this->assertText('Created the Expired Moderation state.');
+
+    // Add a new transition.
+    $this->drupalGet('admin/config/workflow/moderation/transitions');
+    $this->clickLink(t('Add moderation state transition'));
+    $this->drupalPostForm(NULL, [
+      'label' => 'Published » Expired',
+      'id' => 'published_expired',
+      'stateFrom' => 'published',
+      'stateTo' => 'expired',
+    ], t('Save'));
+    $this->assertText('Created the Published » Expired Moderation state transition.');
+
+    // Delete the new transition.
+    $this->drupalGet('admin/config/workflow/moderation/transitions/published_expired');
+    $this->clickLink('Delete');
+    $this->assertText('Are you sure you want to delete Published » Expired?');
+    $this->drupalPostForm(NULL, [], t('Delete'));
+    $this->assertText('Moderation transition Published » Expired deleted');
+  }
+
+}
diff --git a/core/modules/content_moderation/src/Tests/NodeAccessTest.php b/core/modules/content_moderation/src/Tests/NodeAccessTest.php
index 7392a7e..1b05406 100644
--- a/core/modules/content_moderation/src/Tests/NodeAccessTest.php
+++ b/core/modules/content_moderation/src/Tests/NodeAccessTest.php
@@ -15,7 +15,13 @@ class NodeAccessTest extends ModerationStateTestBase {
   protected function setUp() {
     parent::setUp();
     $this->drupalLogin($this->adminUser);
-    $this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE);
+    $this->createContentTypeFromUi(
+      'Moderated content',
+      'moderated_content',
+      TRUE,
+      ['draft', 'published'],
+      'draft'
+    );
     $this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
   }
 
@@ -29,11 +35,20 @@ public function testPageAccess() {
     $this->drupalPostForm('node/add/moderated_content', [
       'title[0][value]' => 'moderated content',
     ], t('Save and Create New Draft'));
-    $node = $this->getNodeByTitle('moderated content');
-    if (!$node) {
+    $nodes = \Drupal::entityTypeManager()
+      ->getStorage('node')
+      ->loadByProperties([
+        'title' => 'moderated content',
+      ]);
+
+    if (!$nodes) {
       $this->fail('Test node was not saved correctly.');
+      return;
     }
 
+    /** @var \Drupal\node\NodeInterface $node */
+    $node = reset($nodes);
+
     $view_path = 'node/' . $node->id();
     $edit_path = 'node/' . $node->id() . '/edit';
     $latest_path = 'node/' . $node->id() . '/latest';
@@ -60,7 +75,8 @@ public function testPageAccess() {
 
     // Now make a new user and verify that the new user's access is correct.
     $user = $this->createUser([
-      'use editorial transition create_new_draft',
+      'use draft_draft transition',
+      'use published_draft transition',
       'view latest version',
       'view any unpublished content',
     ]);
@@ -76,7 +92,7 @@ public function testPageAccess() {
 
     // Now make another user, who should not be able to see forward revisions.
     $user = $this->createUser([
-      'use editorial transition create_new_draft',
+      'use published_draft transition',
     ]);
     $this->drupalLogin($user);
 
diff --git a/core/modules/content_moderation/src/ViewsData.php b/core/modules/content_moderation/src/ViewsData.php
index b357abc..19bcfa5 100644
--- a/core/modules/content_moderation/src/ViewsData.php
+++ b/core/modules/content_moderation/src/ViewsData.php
@@ -204,7 +204,6 @@ public function getViewsData() {
             ],
           ],
         ],
-        'field' => ['default_formatter' => 'content_moderation_state'],
       ];
 
       $revision_table = $entity_type->getRevisionDataTable() ?: $entity_type->getRevisionTable();
@@ -223,7 +222,6 @@ public function getViewsData() {
             ],
           ],
         ],
-        'field' => ['default_formatter' => 'content_moderation_state'],
       ];
     }
 
diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.latest.yml b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.latest.yml
index 62e972e..46a64ab 100644
--- a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.latest.yml
+++ b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.latest.yml
@@ -300,7 +300,9 @@ display:
           empty_zero: false
           hide_alter_empty: true
           click_sort_column: target_id
-          type: content_moderation_state
+          type: entity_reference_label
+          settings:
+            link: true
           group_column: target_id
           group_columns: {  }
           group_rows: true
diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_base_table_test.yml b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_base_table_test.yml
index 343806f..6f95251 100644
--- a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_base_table_test.yml
+++ b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_base_table_test.yml
@@ -193,7 +193,9 @@ display:
           empty_zero: false
           hide_alter_empty: true
           click_sort_column: target_id
-          type: content_moderation_state
+          type: entity_reference_label
+          settings:
+            link: false
           group_column: target_id
           group_columns: {  }
           group_rows: true
@@ -256,7 +258,9 @@ display:
           empty_zero: false
           hide_alter_empty: true
           click_sort_column: target_id
-          type: content_moderation_state
+          type: entity_reference_label
+          settings:
+            link: false
           group_column: target_id
           group_columns: {  }
           group_rows: true
@@ -319,7 +323,8 @@ display:
           empty_zero: false
           hide_alter_empty: true
           click_sort_column: target_id
-          type: string
+          type: entity_reference_entity_id
+          settings: {  }
           group_column: target_id
           group_columns: {  }
           group_rows: true
diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_latest_revision.yml b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_latest_revision.yml
index 4727efa..7673394 100644
--- a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_latest_revision.yml
+++ b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_latest_revision.yml
@@ -306,7 +306,7 @@ display:
           empty_zero: false
           hide_alter_empty: true
           click_sort_column: target_id
-          type: string
+          type: entity_reference_entity_id
           settings: {  }
           group_column: target_id
           group_columns: {  }
@@ -370,7 +370,7 @@ display:
           empty_zero: false
           hide_alter_empty: true
           click_sort_column: target_id
-          type: string
+          type: entity_reference_entity_id
           settings: {  }
           group_column: target_id
           group_columns: {  }
diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_revision_test.yml b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_revision_test.yml
index 78fca38..2362098 100644
--- a/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_revision_test.yml
+++ b/core/modules/content_moderation/tests/modules/content_moderation_test_views/config/install/views.view.test_content_moderation_revision_test.yml
@@ -191,7 +191,7 @@ display:
           empty_zero: false
           hide_alter_empty: true
           click_sort_column: target_id
-          type: string
+          type: entity_reference_entity_id
           settings: {  }
           group_column: target_id
           group_columns: {  }
diff --git a/core/modules/content_moderation/tests/src/Functional/LatestRevisionViewsFilterTest.php b/core/modules/content_moderation/tests/src/Functional/LatestRevisionViewsFilterTest.php
index cf14b23..77ae046 100644
--- a/core/modules/content_moderation/tests/src/Functional/LatestRevisionViewsFilterTest.php
+++ b/core/modules/content_moderation/tests/src/Functional/LatestRevisionViewsFilterTest.php
@@ -5,7 +5,6 @@
 use Drupal\node\Entity\Node;
 use Drupal\node\Entity\NodeType;
 use Drupal\Tests\BrowserTestBase;
-use Drupal\workflows\Entity\Workflow;
 
 /**
  * Tests the "Latest Revision" views filter.
@@ -26,7 +25,7 @@ class LatestRevisionViewsFilterTest extends BrowserTestBase {
    * Tests view shows the correct node IDs.
    */
   public function testViewShowsCorrectNids() {
-    $this->createNodeType('Test', 'test');
+    $node_type = $this->createNodeType('Test', 'test');
 
     $permissions = [
       'access content',
@@ -46,9 +45,8 @@ public function testViewShowsCorrectNids() {
     $node_0->save();
 
     // Now enable moderation for subsequent nodes.
-    $workflow = Workflow::load('editorial');
-    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'test');
-    $workflow->save();
+    $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
+    $node_type->save();
 
     // Make a node that is only ever in Draft.
     /** @var Node $node_1 */
@@ -57,7 +55,7 @@ public function testViewShowsCorrectNids() {
       'title' => 'Node 1 - Rev 1',
       'uid' => $editor1->id(),
     ]);
-    $node_1->moderation_state->value = 'draft';
+    $node_1->moderation_state->target_id = 'draft';
     $node_1->save();
 
     // Make a node that is in Draft, then Published.
@@ -67,11 +65,11 @@ public function testViewShowsCorrectNids() {
       'title' => 'Node 2 - Rev 1',
       'uid' => $editor1->id(),
     ]);
-    $node_2->moderation_state->value = 'draft';
+    $node_2->moderation_state->target_id = 'draft';
     $node_2->save();
 
     $node_2->setTitle('Node 2 - Rev 2');
-    $node_2->moderation_state->value = 'published';
+    $node_2->moderation_state->target_id = 'published';
     $node_2->save();
 
     // Make a node that is in Draft, then Published, then Draft.
@@ -81,15 +79,15 @@ public function testViewShowsCorrectNids() {
       'title' => 'Node 3 - Rev 1',
       'uid' => $editor1->id(),
     ]);
-    $node_3->moderation_state->value = 'draft';
+    $node_3->moderation_state->target_id = 'draft';
     $node_3->save();
 
     $node_3->setTitle('Node 3 - Rev 2');
-    $node_3->moderation_state->value = 'published';
+    $node_3->moderation_state->target_id = 'published';
     $node_3->save();
 
     $node_3->setTitle('Node 3 - Rev 3');
-    $node_3->moderation_state->value = 'draft';
+    $node_3->moderation_state->target_id = 'draft';
     $node_3->save();
 
     // Now show the View, and confirm that only the correct titles are showing.
diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationStateAccessTest.php b/core/modules/content_moderation/tests/src/Functional/ModerationStateAccessTest.php
index 7d2f746..799d89a 100644
--- a/core/modules/content_moderation/tests/src/Functional/ModerationStateAccessTest.php
+++ b/core/modules/content_moderation/tests/src/Functional/ModerationStateAccessTest.php
@@ -5,7 +5,6 @@
 use Drupal\node\Entity\Node;
 use Drupal\node\Entity\NodeType;
 use Drupal\Tests\BrowserTestBase;
-use Drupal\workflows\Entity\Workflow;
 
 /**
  * Tests the view access control handler for moderation state entities.
@@ -32,7 +31,7 @@ public function testViewShowsCorrectStates() {
     $permissions = [
       'access content',
       'view all revisions',
-      'view content moderation',
+      'view moderation states',
     ];
     $editor1 = $this->drupalCreateUser($permissions);
     $this->drupalLogin($editor1);
@@ -42,7 +41,7 @@ public function testViewShowsCorrectStates() {
       'title' => 'Draft node',
       'uid' => $editor1->id(),
     ]);
-    $node_1->moderation_state->value = 'draft';
+    $node_1->moderation_state->target_id = 'draft';
     $node_1->save();
 
     $node_2 = Node::create([
@@ -50,26 +49,26 @@ public function testViewShowsCorrectStates() {
       'title' => 'Published node',
       'uid' => $editor1->id(),
     ]);
-    $node_2->moderation_state->value = 'published';
+    $node_2->moderation_state->target_id = 'published';
     $node_2->save();
 
     // Resave the node with a new state.
     $node_2->setTitle('Archived node');
-    $node_2->moderation_state->value = 'archived';
+    $node_2->moderation_state->target_id = 'archived';
     $node_2->save();
 
     // Now show the View, and confirm that the state labels are showing.
     $this->drupalGet('/latest');
     $page = $this->getSession()->getPage();
-    $this->assertTrue($page->hasContent('Draft'));
-    $this->assertTrue($page->hasContent('Archived'));
-    $this->assertFalse($page->hasContent('Published'));
+    $this->assertTrue($page->hasLink('Draft'));
+    $this->assertTrue($page->hasLink('Archived'));
+    $this->assertFalse($page->hasLink('Published'));
 
     // Now log in as an admin and test the same thing.
     $permissions = [
       'access content',
       'view all revisions',
-      'administer content moderation',
+      'administer moderation states',
     ];
     $admin1 = $this->drupalCreateUser($permissions);
     $this->drupalLogin($admin1);
@@ -77,9 +76,9 @@ public function testViewShowsCorrectStates() {
     $this->drupalGet('/latest');
     $page = $this->getSession()->getPage();
     $this->assertEquals(200, $this->getSession()->getStatusCode());
-    $this->assertTrue($page->hasContent('Draft'));
-    $this->assertTrue($page->hasContent('Archived'));
-    $this->assertFalse($page->hasContent('Published'));
+    $this->assertTrue($page->hasLink('Draft'));
+    $this->assertTrue($page->hasLink('Archived'));
+    $this->assertFalse($page->hasLink('Published'));
   }
 
   /**
@@ -99,11 +98,9 @@ protected function createNodeType($label, $machine_name) {
       'type' => $machine_name,
       'label' => $label,
     ]);
+    $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
     $node_type->save();
 
-    $workflow = Workflow::load('editorial');
-    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', $machine_name);
-    $workflow->save();
     return $node_type;
   }
 
diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationSchemaTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationSchemaTest.php
new file mode 100644
index 0000000..67ce175
--- /dev/null
+++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationSchemaTest.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Drupal\Tests\content_moderation\Kernel;
+
+use Drupal\block_content\Entity\BlockContentType;
+use Drupal\Tests\SchemaCheckTestTrait;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\node\Entity\NodeType;
+use Drupal\content_moderation\Entity\ModerationState;
+use Drupal\content_moderation\Entity\ModerationStateTransition;
+
+/**
+ * Ensures that content moderation schema is correct.
+ *
+ * @group content_moderation
+ */
+class ContentModerationSchemaTest extends KernelTestBase {
+
+  use SchemaCheckTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'content_moderation',
+    'node',
+    'user',
+    'block_content',
+    'system',
+  ];
+
+  /**
+   * Tests content moderation default schema.
+   */
+  public function testContentModerationDefaultConfig() {
+    $this->installConfig(['content_moderation']);
+    $typed_config = \Drupal::service('config.typed');
+    $moderation_states = ModerationState::loadMultiple();
+    foreach ($moderation_states as $moderation_state) {
+      $this->assertConfigSchema($typed_config, $moderation_state->getEntityType()->getConfigPrefix() . '.' . $moderation_state->id(), $moderation_state->toArray());
+    }
+    $moderation_state_transitions = ModerationStateTransition::loadMultiple();
+    foreach ($moderation_state_transitions as $moderation_state_transition) {
+      $this->assertConfigSchema($typed_config, $moderation_state_transition->getEntityType()->getConfigPrefix() . '.' . $moderation_state_transition->id(), $moderation_state_transition->toArray());
+    }
+
+  }
+
+  /**
+   * Tests content moderation third party schema for node types.
+   */
+  public function testContentModerationNodeTypeConfig() {
+    $this->installEntitySchema('node');
+    $this->installEntitySchema('user');
+    $this->installConfig(['content_moderation']);
+    $typed_config = \Drupal::service('config.typed');
+    $moderation_states = ModerationState::loadMultiple();
+    $node_type = NodeType::create([
+      'type' => 'example',
+    ]);
+    $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
+    $node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', array_keys($moderation_states));
+    $node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', '');
+    $node_type->save();
+    $this->assertConfigSchema($typed_config, $node_type->getEntityType()->getConfigPrefix() . '.' . $node_type->id(), $node_type->toArray());
+  }
+
+  /**
+   * Tests content moderation third party schema for block content types.
+   */
+  public function testContentModerationBlockContentTypeConfig() {
+    $this->installEntitySchema('block_content');
+    $this->installEntitySchema('user');
+    $this->installConfig(['content_moderation']);
+    $typed_config = \Drupal::service('config.typed');
+    $moderation_states = ModerationState::loadMultiple();
+    $block_content_type = BlockContentType::create([
+      'id' => 'basic',
+      'label' => 'basic',
+      'revision' => TRUE,
+    ]);
+    $block_content_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
+    $block_content_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', array_keys($moderation_states));
+    $block_content_type->setThirdPartySetting('content_moderation', 'default_moderation_state', '');
+    $block_content_type->save();
+    $this->assertConfigSchema($typed_config, $block_content_type->getEntityType()->getConfigPrefix() . '.' . $block_content_type->id(), $block_content_type->toArray());
+  }
+
+}
diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
index b5b5a75..67a7a74 100644
--- a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\Tests\content_moderation\Kernel;
 
 use Drupal\content_moderation\Entity\ContentModerationState;
+use Drupal\content_moderation\Entity\ModerationState;
 use Drupal\entity_test\Entity\EntityTestBundle;
 use Drupal\entity_test\Entity\EntityTestWithBundle;
 use Drupal\KernelTests\KernelTestBase;
@@ -10,7 +11,6 @@
 use Drupal\node\Entity\Node;
 use Drupal\node\Entity\NodeType;
 use Drupal\node\NodeInterface;
-use Drupal\workflows\Entity\Workflow;
 
 /**
  * Tests links between a content entity and a content_moderation_state entity.
@@ -31,7 +31,6 @@ class ContentModerationStateTest extends KernelTestBase {
     'language',
     'content_translation',
     'text',
-    'workflows',
   ];
 
   /**
@@ -55,25 +54,24 @@ public function testBasicModeration() {
     $node_type = NodeType::create([
       'type' => 'example',
     ]);
+    $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
+    $node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft', 'published']);
+    $node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
     $node_type->save();
-
-    $workflow = Workflow::load('editorial');
-    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
-    $workflow->save();
-
     $node = Node::create([
       'type' => 'example',
       'title' => 'Test title',
     ]);
     $node->save();
     $node = $this->reloadNode($node);
-    $this->assertEquals('draft', $node->moderation_state->value);
+    $this->assertEquals('draft', $node->moderation_state->entity->id());
 
-    $node->moderation_state->value = 'published';
+    $published = ModerationState::load('published');
+    $node->moderation_state->entity = $published;
     $node->save();
 
     $node = $this->reloadNode($node);
-    $this->assertEquals('published', $node->moderation_state->value);
+    $this->assertEquals('published', $node->moderation_state->entity->id());
 
     // Change the state without saving the node.
     $content_moderation_state = ContentModerationState::load(1);
@@ -82,7 +80,7 @@ public function testBasicModeration() {
     $content_moderation_state->save();
 
     $node = $this->reloadNode($node, 3);
-    $this->assertEquals('draft', $node->moderation_state->value);
+    $this->assertEquals('draft', $node->moderation_state->entity->id());
     $this->assertFalse($node->isPublished());
 
     // Get the default revision.
@@ -90,11 +88,11 @@ public function testBasicModeration() {
     $this->assertTrue($node->isPublished());
     $this->assertEquals(2, $node->getRevisionId());
 
-    $node->moderation_state->value = 'published';
+    $node->moderation_state->target_id = 'published';
     $node->save();
 
     $node = $this->reloadNode($node, 4);
-    $this->assertEquals('published', $node->moderation_state->value);
+    $this->assertEquals('published', $node->moderation_state->entity->id());
 
     // Get the default revision.
     $node = $this->reloadNode($node);
@@ -112,12 +110,10 @@ public function testMultilingualModeration() {
     $node_type = NodeType::create([
       'type' => 'example',
     ]);
+    $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
+    $node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft', 'published']);
+    $node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
     $node_type->save();
-
-    $workflow = Workflow::load('editorial');
-    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
-    $workflow->save();
-
     $english_node = Node::create([
       'type' => 'example',
       'title' => 'Test title',
@@ -126,7 +122,7 @@ public function testMultilingualModeration() {
     $english_node
       ->setPublished(FALSE)
       ->save();
-    $this->assertEquals('draft', $english_node->moderation_state->value);
+    $this->assertEquals('draft', $english_node->moderation_state->entity->id());
     $this->assertFalse($english_node->isPublished());
 
     // Create a French translation.
@@ -135,34 +131,34 @@ public function testMultilingualModeration() {
     // Revision 1 (fr).
     $french_node->save();
     $french_node = $this->reloadNode($english_node)->getTranslation('fr');
-    $this->assertEquals('draft', $french_node->moderation_state->value);
+    $this->assertEquals('draft', $french_node->moderation_state->entity->id());
     $this->assertFalse($french_node->isPublished());
 
     // Move English node to create another draft.
     $english_node = $this->reloadNode($english_node);
-    $english_node->moderation_state->value = 'draft';
+    $english_node->moderation_state->target_id = 'draft';
     // Revision 2 (en, fr).
     $english_node->save();
     $english_node = $this->reloadNode($english_node);
-    $this->assertEquals('draft', $english_node->moderation_state->value);
+    $this->assertEquals('draft', $english_node->moderation_state->entity->id());
 
     // French node should still be in draft.
     $french_node = $this->reloadNode($english_node)->getTranslation('fr');
-    $this->assertEquals('draft', $french_node->moderation_state->value);
+    $this->assertEquals('draft', $french_node->moderation_state->entity->id());
 
     // Publish the French node.
-    $french_node->moderation_state->value = 'published';
+    $french_node->moderation_state->target_id = 'published';
     // Revision 3 (en, fr).
     $french_node->save();
     $french_node = $this->reloadNode($french_node)->getTranslation('fr');
     $this->assertTrue($french_node->isPublished());
-    $this->assertEquals('published', $french_node->moderation_state->value);
+    $this->assertEquals('published', $french_node->moderation_state->entity->id());
     $this->assertTrue($french_node->isPublished());
     $english_node = $french_node->getTranslation('en');
-    $this->assertEquals('draft', $english_node->moderation_state->value);
+    $this->assertEquals('draft', $english_node->moderation_state->entity->id());
 
     // Publish the English node.
-    $english_node->moderation_state->value = 'published';
+    $english_node->moderation_state->target_id = 'published';
     // Revision 4 (en, fr).
     $english_node->save();
     $english_node = $this->reloadNode($english_node);
@@ -171,7 +167,7 @@ public function testMultilingualModeration() {
     // Move the French node back to draft.
     $french_node = $this->reloadNode($english_node)->getTranslation('fr');
     $this->assertTrue($french_node->isPublished());
-    $french_node->moderation_state->value = 'draft';
+    $french_node->moderation_state->target_id = 'draft';
     // Revision 5 (en, fr).
     $french_node->save();
     $french_node = $this->reloadNode($english_node, 5)->getTranslation('fr');
@@ -179,7 +175,7 @@ public function testMultilingualModeration() {
     $this->assertTrue($french_node->getTranslation('en')->isPublished());
 
     // Republish the French node.
-    $french_node->moderation_state->value = 'published';
+    $french_node->moderation_state->target_id = 'published';
     // Revision 6 (en, fr).
     $french_node->save();
     $french_node = $this->reloadNode($english_node)->getTranslation('fr');
@@ -193,9 +189,9 @@ public function testMultilingualModeration() {
     $content_moderation_state->save();
     $english_node = $this->reloadNode($french_node, $french_node->getRevisionId() + 1);
 
-    $this->assertEquals('draft', $english_node->moderation_state->value);
+    $this->assertEquals('draft', $english_node->moderation_state->entity->id());
     $french_node = $this->reloadNode($english_node)->getTranslation('fr');
-    $this->assertEquals('published', $french_node->moderation_state->value);
+    $this->assertEquals('published', $french_node->moderation_state->entity->id());
 
     // This should unpublish the French node.
     $content_moderation_state = ContentModerationState::load(1);
@@ -206,9 +202,9 @@ public function testMultilingualModeration() {
     $content_moderation_state->save();
 
     $english_node = $this->reloadNode($english_node, $english_node->getRevisionId());
-    $this->assertEquals('draft', $english_node->moderation_state->value);
+    $this->assertEquals('draft', $english_node->moderation_state->entity->id());
     $french_node = $this->reloadNode($english_node, '8')->getTranslation('fr');
-    $this->assertEquals('draft', $french_node->moderation_state->value);
+    $this->assertEquals('draft', $french_node->moderation_state->entity->id());
     // Switching the moderation state to an unpublished state should update the
     // entity.
     $this->assertFalse($french_node->isPublished());
@@ -235,12 +231,14 @@ public function testNonTranslatableEntityTypeModeration() {
     $entity_test_bundle = EntityTestBundle::create([
       'id' => 'example',
     ]);
+    $entity_test_bundle->setThirdPartySetting('content_moderation', 'enabled', TRUE);
+    $entity_test_bundle->setThirdPartySetting('content_moderation', 'allowed_moderation_states', [
+      'draft',
+      'published'
+    ]);
+    $entity_test_bundle->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
     $entity_test_bundle->save();
 
-    $workflow = Workflow::load('editorial');
-    $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_with_bundle', 'example');
-    $workflow->save();
-
     // Check that the tested entity type is not translatable.
     $entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
     $this->assertFalse($entity_type->isTranslatable(), 'The test entity type is not translatable.');
@@ -250,12 +248,12 @@ public function testNonTranslatableEntityTypeModeration() {
       'type' => 'example'
     ]);
     $entity_test_with_bundle->save();
-    $this->assertEquals('draft', $entity_test_with_bundle->moderation_state->value);
+    $this->assertEquals('draft', $entity_test_with_bundle->moderation_state->entity->id());
 
-    $entity_test_with_bundle->moderation_state->value = 'published';
+    $entity_test_with_bundle->moderation_state->target_id = 'published';
     $entity_test_with_bundle->save();
 
-    $this->assertEquals('published', EntityTestWithBundle::load($entity_test_with_bundle->id())->moderation_state->value);
+    $this->assertEquals('published', EntityTestWithBundle::load($entity_test_with_bundle->id())->moderation_state->entity->id());
   }
 
   /**
@@ -277,12 +275,14 @@ public function testNonLangcodeEntityTypeModeration() {
     $entity_test_bundle = EntityTestBundle::create([
       'id' => 'example',
     ]);
+    $entity_test_bundle->setThirdPartySetting('content_moderation', 'enabled', TRUE);
+    $entity_test_bundle->setThirdPartySetting('content_moderation', 'allowed_moderation_states', [
+      'draft',
+      'published'
+    ]);
+    $entity_test_bundle->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
     $entity_test_bundle->save();
 
-    $workflow = Workflow::load('editorial');
-    $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_with_bundle', 'example');
-    $workflow->save();
-
     // Check that the tested entity type is not translatable.
     $entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_with_bundle');
     $this->assertFalse($entity_type->isTranslatable(), 'The test entity type is not translatable.');
@@ -292,12 +292,12 @@ public function testNonLangcodeEntityTypeModeration() {
       'type' => 'example'
     ]);
     $entity_test_with_bundle->save();
-    $this->assertEquals('draft', $entity_test_with_bundle->moderation_state->value);
+    $this->assertEquals('draft', $entity_test_with_bundle->moderation_state->entity->id());
 
-    $entity_test_with_bundle->moderation_state->value = 'published';
+    $entity_test_with_bundle->moderation_state->target_id = 'published';
     $entity_test_with_bundle->save();
 
-    $this->assertEquals('published', EntityTestWithBundle::load($entity_test_with_bundle->id())->moderation_state->value);
+    $this->assertEquals('published', EntityTestWithBundle::load($entity_test_with_bundle->id())->moderation_state->entity->id());
   }
 
   /**
diff --git a/core/modules/content_moderation/tests/src/Kernel/EntityOperationsTest.php b/core/modules/content_moderation/tests/src/Kernel/EntityOperationsTest.php
index 60e9edf..929356e 100644
--- a/core/modules/content_moderation/tests/src/Kernel/EntityOperationsTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/EntityOperationsTest.php
@@ -4,9 +4,9 @@
 
 
 use Drupal\KernelTests\KernelTestBase;
+use Drupal\content_moderation\Entity\ModerationState;
 use Drupal\node\Entity\Node;
 use Drupal\node\Entity\NodeType;
-use Drupal\workflows\Entity\Workflow;
 
 /**
  * @coversDefaultClass \Drupal\content_moderation\EntityOperations
@@ -23,7 +23,6 @@ class EntityOperationsTest extends KernelTestBase {
     'node',
     'user',
     'system',
-    'workflows',
   ];
 
   /**
@@ -48,10 +47,8 @@ protected function createNodeType() {
       'type' => 'page',
       'label' => 'Page',
     ]);
+    $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
     $node_type->save();
-    $workflow = Workflow::load('editorial');
-    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'page');
-    $workflow->save();
   }
 
   /**
@@ -63,7 +60,7 @@ public function testForwardRevisions() {
       'type' => 'page',
       'title' => 'A',
     ]);
-    $page->moderation_state->value = 'draft';
+    $page->moderation_state->target_id = 'draft';
     $page->save();
 
     $id = $page->id();
@@ -78,7 +75,7 @@ public function testForwardRevisions() {
 
     // Moderate the entity to published.
     $page->setTitle('B');
-    $page->moderation_state->value = 'published';
+    $page->moderation_state->target_id = 'published';
     $page->save();
 
     // Verify the entity is now published and public.
@@ -89,7 +86,7 @@ public function testForwardRevisions() {
 
     // Make a new forward-revision in Draft.
     $page->setTitle('C');
-    $page->moderation_state->value = 'draft';
+    $page->moderation_state->target_id = 'draft';
     $page->save();
 
     // Verify normal loads return the still-default previous version.
@@ -108,7 +105,7 @@ public function testForwardRevisions() {
     $this->assertEquals('C', $page->getTitle());
 
     $page->setTitle('D');
-    $page->moderation_state->value = 'published';
+    $page->moderation_state->target_id = 'published';
     $page->save();
 
     // Verify normal loads return the still-default previous version.
@@ -119,7 +116,7 @@ public function testForwardRevisions() {
 
     // Now check that we can immediately add a new published revision over it.
     $page->setTitle('E');
-    $page->moderation_state->value = 'published';
+    $page->moderation_state->target_id = 'published';
     $page->save();
 
     $page = Node::load($id);
@@ -137,7 +134,7 @@ public function testPublishedCreation() {
       'type' => 'page',
       'title' => 'A',
     ]);
-    $page->moderation_state->value = 'published';
+    $page->moderation_state->target_id = 'published';
     $page->save();
 
     $id = $page->id();
@@ -154,12 +151,29 @@ public function testPublishedCreation() {
    * Verifies that an unpublished state may be made the default revision.
    */
   public function testArchive() {
+    $published_id = $this->randomMachineName();
+    $published_state = ModerationState::create([
+      'id' => $published_id,
+      'label' => $this->randomString(),
+      'published' => TRUE,
+      'default_revision' => TRUE,
+    ]);
+    $published_state->save();
+
+    $archived_id = $this->randomMachineName();
+    $archived_state = ModerationState::create([
+      'id' => $archived_id,
+      'label' => $this->randomString(),
+      'published' => FALSE,
+      'default_revision' => TRUE,
+    ]);
+    $archived_state->save();
+
     $page = Node::create([
       'type' => 'page',
       'title' => $this->randomString(),
     ]);
-
-    $page->moderation_state->value = 'published';
+    $page->moderation_state->target_id = $published_id;
     $page->save();
 
     $id = $page->id();
@@ -170,7 +184,7 @@ public function testArchive() {
 
     // When the page is moderated to the archived state, then the latest
     // revision should be the default revision, and it should be unpublished.
-    $page->moderation_state->value = 'archived';
+    $page->moderation_state->target_id = $archived_id;
     $page->save();
     $new_revision_id = $page->getRevisionId();
 
diff --git a/core/modules/content_moderation/tests/src/Kernel/EntityRevisionConverterTest.php b/core/modules/content_moderation/tests/src/Kernel/EntityRevisionConverterTest.php
index 6f209d1..89c84f9 100644
--- a/core/modules/content_moderation/tests/src/Kernel/EntityRevisionConverterTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/EntityRevisionConverterTest.php
@@ -6,7 +6,6 @@
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\node\Entity\Node;
 use Drupal\node\Entity\NodeType;
-use Drupal\workflows\Entity\Workflow;
 
 /**
  * @coversDefaultClass \Drupal\content_moderation\ParamConverter\EntityRevisionConverter
@@ -20,7 +19,6 @@ class EntityRevisionConverterTest extends KernelTestBase {
     'system',
     'content_moderation',
     'node',
-    'workflows',
   ];
 
   /**
@@ -61,21 +59,17 @@ public function testConvertNonRevisionableEntityType() {
    * @covers ::convert
    */
   public function testConvertWithRevisionableEntityType() {
-    $this->installConfig(['content_moderation']);
     $node_type = NodeType::create([
       'type' => 'article',
     ]);
+    $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
     $node_type->save();
-    $workflow = Workflow::load('editorial');
-    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'article');
-    $workflow->save();
 
     $revision_ids = [];
     $node = Node::create([
       'title' => 'test',
       'type' => 'article',
     ]);
-    $node->moderation_state->value = 'published';
     $node->save();
 
     $revision_ids[] = $node->getRevisionId();
@@ -85,7 +79,7 @@ public function testConvertWithRevisionableEntityType() {
     $revision_ids[] = $node->getRevisionId();
 
     $node->setNewRevision(TRUE);
-    $node->moderation_state->value = 'draft';
+    $node->isDefaultRevision(FALSE);
     $node->save();
     $revision_ids[] = $node->getRevisionId();
 
diff --git a/core/modules/content_moderation/tests/src/Kernel/EntityStateChangeValidationTest.php b/core/modules/content_moderation/tests/src/Kernel/EntityStateChangeValidationTest.php
index 7c4f97d..97e61f1 100644
--- a/core/modules/content_moderation/tests/src/Kernel/EntityStateChangeValidationTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/EntityStateChangeValidationTest.php
@@ -6,7 +6,6 @@
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\node\Entity\Node;
 use Drupal\node\Entity\NodeType;
-use Drupal\workflows\Entity\Workflow;
 
 /**
  * @coversDefaultClass \Drupal\content_moderation\Plugin\Validation\Constraint\ModerationStateConstraintValidator
@@ -24,7 +23,6 @@ class EntityStateChangeValidationTest extends KernelTestBase {
     'system',
     'language',
     'content_translation',
-    'workflows',
   ];
 
   /**
@@ -49,23 +47,20 @@ public function testValidTransition() {
     $node_type = NodeType::create([
       'type' => 'example',
     ]);
+    $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
     $node_type->save();
-    $workflow = Workflow::load('editorial');
-    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
-    $workflow->save();
-
     $node = Node::create([
       'type' => 'example',
       'title' => 'Test title',
     ]);
-    $node->moderation_state->value = 'draft';
+    $node->moderation_state->target_id = 'draft';
     $node->save();
 
-    $node->moderation_state->value = 'published';
+    $node->moderation_state->target_id = 'published';
     $this->assertCount(0, $node->validate());
     $node->save();
 
-    $this->assertEquals('published', $node->moderation_state->value);
+    $this->assertEquals('published', $node->moderation_state->entity->id());
   }
 
   /**
@@ -77,19 +72,16 @@ public function testInvalidTransition() {
     $node_type = NodeType::create([
       'type' => 'example',
     ]);
+    $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
     $node_type->save();
-    $workflow = Workflow::load('editorial');
-    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
-    $workflow->save();
-
     $node = Node::create([
       'type' => 'example',
       'title' => 'Test title',
     ]);
-    $node->moderation_state->value = 'draft';
+    $node->moderation_state->target_id = 'draft';
     $node->save();
 
-    $node->moderation_state->value = 'archived';
+    $node->moderation_state->target_id = 'archived';
     $violations = $node->validate();
     $this->assertCount(1, $violations);
 
@@ -114,9 +106,12 @@ public function testLegacyContent() {
     $nid = $node->id();
 
     // Enable moderation for our node type.
-    $workflow = Workflow::load('editorial');
-    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
-    $workflow->save();
+    /** @var NodeType $node_type */
+    $node_type = NodeType::load('example');
+    $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
+    $node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft', 'published']);
+    $node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
+    $node_type->save();
 
     $node = Node::load($nid);
 
@@ -160,9 +155,12 @@ public function testLegacyMultilingualContent() {
     $node_fr->save();
 
     // Enable moderation for our node type.
-    $workflow = Workflow::load('editorial');
-    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
-    $workflow->save();
+    /** @var NodeType $node_type */
+    $node_type = NodeType::load('example');
+    $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
+    $node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft', 'published']);
+    $node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
+    $node_type->save();
 
     // Reload the French version of the node.
     $node = Node::load($nid);
diff --git a/core/modules/content_moderation/tests/src/Kernel/ModerationStateEntityTest.php b/core/modules/content_moderation/tests/src/Kernel/ModerationStateEntityTest.php
new file mode 100644
index 0000000..f312cde
--- /dev/null
+++ b/core/modules/content_moderation/tests/src/Kernel/ModerationStateEntityTest.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\Tests\content_moderation\Kernel;
+
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\content_moderation\Entity\ModerationState;
+
+/**
+ * @coversDefaultClass \Drupal\content_moderation\Entity\ModerationState
+ *
+ * @group content_moderation
+ */
+class ModerationStateEntityTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['content_moderation'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('moderation_state');
+  }
+
+  /**
+   * Verify moderation state methods based on entity properties.
+   *
+   * @covers ::isPublishedState
+   * @covers ::isDefaultRevisionState
+   *
+   * @dataProvider moderationStateProvider
+   */
+  public function testModerationStateProperties($published, $default_revision, $is_published, $is_default) {
+    $moderation_state_id = $this->randomMachineName();
+    $moderation_state = ModerationState::create([
+      'id' => $moderation_state_id,
+      'label' => $this->randomString(),
+      'published' => $published,
+      'default_revision' => $default_revision,
+    ]);
+    $moderation_state->save();
+
+    $moderation_state = ModerationState::load($moderation_state_id);
+    $this->assertEquals($is_published, $moderation_state->isPublishedState());
+    $this->assertEquals($is_default, $moderation_state->isDefaultRevisionState());
+  }
+
+  /**
+   * Data provider for ::testModerationStateProperties.
+   */
+  public function moderationStateProvider() {
+    return [
+      // Draft, Needs review; should not touch the default revision.
+      [FALSE, FALSE, FALSE, FALSE],
+      // Published; this state should update and publish the default revision.
+      [TRUE, TRUE, TRUE, TRUE],
+      // Archive; this state should update but not publish the default revision.
+      [FALSE, TRUE, FALSE, TRUE],
+      // We try to prevent creating this state via the UI, but when a moderation
+      // state is a published state, it should also become the default revision.
+      [TRUE, FALSE, TRUE, TRUE],
+    ];
+  }
+
+}
diff --git a/core/modules/content_moderation/tests/src/Kernel/ModerationStateFieldItemListTest.php b/core/modules/content_moderation/tests/src/Kernel/ModerationStateFieldItemListTest.php
index 3f984da..c57963e 100644
--- a/core/modules/content_moderation/tests/src/Kernel/ModerationStateFieldItemListTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/ModerationStateFieldItemListTest.php
@@ -5,7 +5,6 @@
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\node\Entity\Node;
 use Drupal\node\Entity\NodeType;
-use Drupal\workflows\Entity\Workflow;
 
 /**
  * @coversDefaultClass \Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList
@@ -23,7 +22,6 @@ class ModerationStateFieldItemListTest extends KernelTestBase {
     'user',
     'system',
     'language',
-    'workflows',
   ];
 
   /**
@@ -46,11 +44,10 @@ protected function setUp() {
     $node_type = NodeType::create([
       'type' => 'example',
     ]);
+    $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
+    $node_type->setThirdPartySetting('content_moderation', 'allowed_moderation_states', ['draft']);
+    $node_type->setThirdPartySetting('content_moderation', 'default_moderation_state', 'draft');
     $node_type->save();
-    $workflow = Workflow::load('editorial');
-    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
-    $workflow->save();
-
     $this->testNode = Node::create([
       'type' => 'example',
       'title' => 'Test title',
@@ -64,7 +61,7 @@ protected function setUp() {
    * Test the field item list when accessing an index.
    */
   public function testArrayIndex() {
-    $this->assertEquals('draft', $this->testNode->moderation_state[0]->value);
+    $this->assertEquals('draft', $this->testNode->moderation_state[0]->entity->id());
   }
 
   /**
@@ -73,7 +70,7 @@ public function testArrayIndex() {
   public function testArrayIteration() {
     $states = [];
     foreach ($this->testNode->moderation_state as $item) {
-      $states[] = $item->value;
+      $states[] = $item->entity->id();
     }
     $this->assertEquals(['draft'], $states);
   }
diff --git a/core/modules/content_moderation/tests/src/Kernel/ViewsDataIntegrationTest.php b/core/modules/content_moderation/tests/src/Kernel/ViewsDataIntegrationTest.php
index 6b127c8..c869619 100644
--- a/core/modules/content_moderation/tests/src/Kernel/ViewsDataIntegrationTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/ViewsDataIntegrationTest.php
@@ -6,7 +6,6 @@
 use Drupal\node\Entity\NodeType;
 use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
 use Drupal\views\Views;
-use Drupal\workflows\Entity\Workflow;
 
 /**
  * Tests the views integration of content_moderation.
@@ -22,7 +21,6 @@ class ViewsDataIntegrationTest extends ViewsKernelTestBase {
     'content_moderation_test_views',
     'node',
     'content_moderation',
-    'workflows',
   ];
 
   /**
@@ -41,10 +39,8 @@ protected function setUp($import_test_views = TRUE) {
     $node_type = NodeType::create([
       'type' => 'page',
     ]);
+    $node_type->setThirdPartySetting('content_moderation', 'enabled', TRUE);
     $node_type->save();
-    $workflow = Workflow::load('editorial');
-    $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'page');
-    $workflow->save();
   }
 
   /**
@@ -57,14 +53,14 @@ public function testViewsData() {
       'type' => 'page',
       'title' => 'Test title first revision',
     ]);
-    $node->moderation_state->value = 'published';
+    $node->moderation_state->target_id = 'published';
     $node->save();
 
     $revision = clone $node;
     $revision->setNewRevision(TRUE);
     $revision->isDefaultRevision(FALSE);
     $revision->title->value = 'Test title second revision';
-    $revision->moderation_state->value = 'draft';
+    $revision->moderation_state->target_id = 'draft';
     $revision->save();
 
     $view = Views::getView('test_content_moderation_latest_revision');
@@ -94,14 +90,14 @@ public function testContentModerationStateRevisionJoin() {
       'type' => 'page',
       'title' => 'Test title first revision',
     ]);
-    $node->moderation_state->value = 'published';
+    $node->moderation_state->target_id = 'published';
     $node->save();
 
     $revision = clone $node;
     $revision->setNewRevision(TRUE);
     $revision->isDefaultRevision(FALSE);
     $revision->title->value = 'Test title second revision';
-    $revision->moderation_state->value = 'draft';
+    $revision->moderation_state->target_id = 'draft';
     $revision->save();
 
     $view = Views::getView('test_content_moderation_revision_test');
@@ -128,14 +124,14 @@ public function testContentModerationStateBaseJoin() {
       'type' => 'page',
       'title' => 'Test title first revision',
     ]);
-    $node->moderation_state->value = 'published';
+    $node->moderation_state->target_id = 'published';
     $node->save();
 
     $revision = clone $node;
     $revision->setNewRevision(TRUE);
     $revision->isDefaultRevision(FALSE);
     $revision->title->value = 'Test title second revision';
-    $revision->moderation_state->value = 'draft';
+    $revision->moderation_state->target_id = 'draft';
     $revision->save();
 
     $view = Views::getView('test_content_moderation_base_table_test');
diff --git a/core/modules/content_moderation/tests/src/Unit/ModerationInformationTest.php b/core/modules/content_moderation/tests/src/Unit/ModerationInformationTest.php
index d7a7373..ff46e41 100644
--- a/core/modules/content_moderation/tests/src/Unit/ModerationInformationTest.php
+++ b/core/modules/content_moderation/tests/src/Unit/ModerationInformationTest.php
@@ -3,14 +3,13 @@
 namespace Drupal\Tests\content_moderation\Unit;
 
 use Drupal\content_moderation\Entity\Handler\ModerationHandler;
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\ContentEntityType;
 use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\content_moderation\ModerationInformation;
-use Drupal\workflows\WorkflowInterface;
 
 /**
  * @coversDefaultClass \Drupal\content_moderation\ModerationInformation
@@ -31,42 +30,43 @@ protected function getUser() {
   /**
    * Returns a mock Entity Type Manager.
    *
+   * @param \Drupal\Core\Entity\EntityStorageInterface $entity_bundle_storage
+   *   Entity bundle storage.
+   *
    * @return EntityTypeManagerInterface
    *   The mocked entity type manager.
    */
-  protected function getEntityTypeManager() {
+  protected function getEntityTypeManager(EntityStorageInterface $entity_bundle_storage) {
     $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
+    $entity_type_manager->getStorage('entity_test_bundle')->willReturn($entity_bundle_storage);
     return $entity_type_manager->reveal();
   }
 
   /**
    * Sets up content moderation and entity manager mocking.
    *
-   * @param string $bundle
-   *   The bundle ID.
-   * @param string|null $workflow
-   *   The workflow ID. If nul no workflow information is added to the bundle.
+   * @param bool $status
+   *   TRUE if content_moderation should be enabled, FALSE if not.
    *
    * @return \Drupal\Core\Entity\EntityTypeManagerInterface
    *   The mocked entity type manager.
    */
-  public function setupModerationBundleInfo($bundle, $workflow = NULL) {
-    $bundle_info_array = [];
-    if ($workflow) {
-      $bundle_info_array['workflow'] = $workflow;
-    }
-    $bundle_info = $this->prophesize(EntityTypeBundleInfoInterface::class);
-    $bundle_info->getBundleInfo("test_entity_type")->willReturn([$bundle => $bundle_info_array]);
-
-    return $bundle_info->reveal();
+  public function setupModerationEntityManager($status) {
+    $bundle = $this->prophesize(ConfigEntityInterface::class);
+    $bundle->getThirdPartySetting('content_moderation', 'enabled', FALSE)->willReturn($status);
+
+    $entity_storage = $this->prophesize(EntityStorageInterface::class);
+    $entity_storage->load('test_bundle')->willReturn($bundle->reveal());
+
+    return $this->getEntityTypeManager($entity_storage->reveal());
   }
 
   /**
-   * @dataProvider providerWorkflow
+   * @dataProvider providerBoolean
    * @covers ::isModeratedEntity
    */
-  public function testIsModeratedEntity($workflow, $expected) {
-    $moderation_information = new ModerationInformation($this->getEntityTypeManager(), $this->setupModerationBundleInfo('test_bundle', $workflow));
+  public function testIsModeratedEntity($status) {
+    $moderation_information = new ModerationInformation($this->setupModerationEntityManager($status), $this->getUser());
 
     $entity_type = new ContentEntityType([
       'id' => 'test_entity_type',
@@ -77,55 +77,50 @@ public function testIsModeratedEntity($workflow, $expected) {
     $entity->getEntityType()->willReturn($entity_type);
     $entity->bundle()->willReturn('test_bundle');
 
-    $this->assertEquals($expected, $moderation_information->isModeratedEntity($entity->reveal()));
+    $this->assertEquals($status, $moderation_information->isModeratedEntity($entity->reveal()));
   }
 
   /**
-   * @dataProvider providerWorkflow
-   * @covers ::getWorkFlowForEntity
+   * @covers ::isModeratedEntity
    */
-  public function testGetWorkFlowForEntity($workflow) {
-    $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
-    if ($workflow) {
-      $workflow_entity = $this->prophesize(WorkflowInterface::class)->reveal();
-      $workflow_storage = $this->prophesize(EntityStorageInterface::class);
-      $workflow_storage->load('workflow')->willReturn($workflow_entity)->shouldBeCalled();
-      $entity_type_manager->getStorage('workflow')->willReturn($workflow_storage->reveal());
-    }
-    else {
-      $workflow_entity = NULL;
-    }
-    $moderation_information = new ModerationInformation($entity_type_manager->reveal(), $this->setupModerationBundleInfo('test_bundle', $workflow));
+  public function testIsModeratedEntityForNonBundleEntityType() {
+    $entity_type = new ContentEntityType([
+      'id' => 'test_entity_type',
+    ]);
     $entity = $this->prophesize(ContentEntityInterface::class);
-    $entity->getEntityTypeId()->willReturn('test_entity_type');
-    $entity->bundle()->willReturn('test_bundle');
+    $entity->getEntityType()->willReturn($entity_type);
+    $entity->bundle()->willReturn('test_entity_type');
+
+    $entity_storage = $this->prophesize(EntityStorageInterface::class);
+    $entity_type_manager = $this->getEntityTypeManager($entity_storage->reveal());
+    $moderation_information = new ModerationInformation($entity_type_manager, $this->getUser());
 
-    $this->assertEquals($workflow_entity, $moderation_information->getWorkFlowForEntity($entity->reveal()));
+    $this->assertEquals(FALSE, $moderation_information->isModeratedEntity($entity->reveal()));
   }
 
   /**
-   * @dataProvider providerWorkflow
+   * @dataProvider providerBoolean
    * @covers ::shouldModerateEntitiesOfBundle
    */
-  public function testShouldModerateEntities($workflow, $expected) {
+  public function testShouldModerateEntities($status) {
     $entity_type = new ContentEntityType([
       'id' => 'test_entity_type',
       'bundle_entity_type' => 'entity_test_bundle',
       'handlers' => ['moderation' => ModerationHandler::class],
     ]);
 
-    $moderation_information = new ModerationInformation($this->getEntityTypeManager(), $this->setupModerationBundleInfo('test_bundle', $workflow));
+    $moderation_information = new ModerationInformation($this->setupModerationEntityManager($status), $this->getUser());
 
-    $this->assertEquals($expected, $moderation_information->shouldModerateEntitiesOfBundle($entity_type, 'test_bundle'));
+    $this->assertEquals($status, $moderation_information->shouldModerateEntitiesOfBundle($entity_type, 'test_bundle'));
   }
 
   /**
    * Data provider for several tests.
    */
-  public function providerWorkflow() {
+  public function providerBoolean() {
     return [
-      [NULL, FALSE],
-      ['workflow', TRUE],
+      [FALSE],
+      [TRUE],
     ];
   }
 
diff --git a/core/modules/content_moderation/tests/src/Unit/StateTransitionValidationTest.php b/core/modules/content_moderation/tests/src/Unit/StateTransitionValidationTest.php
index e518aed..b057478 100644
--- a/core/modules/content_moderation/tests/src/Unit/StateTransitionValidationTest.php
+++ b/core/modules/content_moderation/tests/src/Unit/StateTransitionValidationTest.php
@@ -2,14 +2,13 @@
 
 namespace Drupal\Tests\content_moderation\Unit;
 
-use Drupal\content_moderation\ModerationInformationInterface;
-use Drupal\Core\DependencyInjection\ContainerBuilder;
-use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\content_moderation\ModerationStateInterface;
+use Drupal\content_moderation\ModerationStateTransitionInterface;
 use Drupal\content_moderation\StateTransitionValidation;
-use Drupal\workflows\Entity\Workflow;
-use Drupal\workflows\WorkflowTypeInterface;
-use Drupal\workflows\WorkflowTypeManager;
 use Prophecy\Argument;
 
 /**
@@ -19,6 +18,216 @@
 class StateTransitionValidationTest extends \PHPUnit_Framework_TestCase {
 
   /**
+   * Builds a mock storage object for Transitions.
+   *
+   * @return EntityStorageInterface
+   *   The mocked storage object for Transitions.
+   */
+  protected function setupTransitionStorage() {
+    $entity_storage = $this->prophesize(EntityStorageInterface::class);
+
+    $list = $this->setupTransitionEntityList();
+    $entity_storage->loadMultiple()->willReturn($list);
+    $entity_storage->loadMultiple(Argument::type('array'))->will(function ($args) use ($list) {
+      $keys = $args[0];
+      if (empty($keys)) {
+        return $list;
+      }
+
+      $return = array_map(function($key) use ($list) {
+        return $list[$key];
+      }, $keys);
+
+      return $return;
+    });
+    return $entity_storage->reveal();
+  }
+
+  /**
+   * Builds an array of mocked Transition objects.
+   *
+   * @return ModerationStateTransitionInterface[]
+   *   An array of mocked Transition objects.
+   */
+  protected function setupTransitionEntityList() {
+    $transition = $this->prophesize(ModerationStateTransitionInterface::class);
+    $transition->id()->willReturn('draft__needs_review');
+    $transition->getFromState()->willReturn('draft');
+    $transition->getToState()->willReturn('needs_review');
+    $list[$transition->reveal()->id()] = $transition->reveal();
+
+    $transition = $this->prophesize(ModerationStateTransitionInterface::class);
+    $transition->id()->willReturn('needs_review__staging');
+    $transition->getFromState()->willReturn('needs_review');
+    $transition->getToState()->willReturn('staging');
+    $list[$transition->reveal()->id()] = $transition->reveal();
+
+    $transition = $this->prophesize(ModerationStateTransitionInterface::class);
+    $transition->id()->willReturn('staging__published');
+    $transition->getFromState()->willReturn('staging');
+    $transition->getToState()->willReturn('published');
+    $list[$transition->reveal()->id()] = $transition->reveal();
+
+    $transition = $this->prophesize(ModerationStateTransitionInterface::class);
+    $transition->id()->willReturn('needs_review__draft');
+    $transition->getFromState()->willReturn('needs_review');
+    $transition->getToState()->willReturn('draft');
+    $list[$transition->reveal()->id()] = $transition->reveal();
+
+    $transition = $this->prophesize(ModerationStateTransitionInterface::class);
+    $transition->id()->willReturn('draft__draft');
+    $transition->getFromState()->willReturn('draft');
+    $transition->getToState()->willReturn('draft');
+    $list[$transition->reveal()->id()] = $transition->reveal();
+
+    $transition = $this->prophesize(ModerationStateTransitionInterface::class);
+    $transition->id()->willReturn('needs_review__needs_review');
+    $transition->getFromState()->willReturn('needs_review');
+    $transition->getToState()->willReturn('needs_review');
+    $list[$transition->reveal()->id()] = $transition->reveal();
+
+    $transition = $this->prophesize(ModerationStateTransitionInterface::class);
+    $transition->id()->willReturn('published__published');
+    $transition->getFromState()->willReturn('published');
+    $transition->getToState()->willReturn('published');
+    $list[$transition->reveal()->id()] = $transition->reveal();
+
+    return $list;
+  }
+
+  /**
+   * Builds a mock storage object for States.
+   *
+   * @return EntityStorageInterface
+   *   The mocked storage object for States.
+   */
+  protected function setupStateStorage() {
+    $entity_storage = $this->prophesize(EntityStorageInterface::class);
+
+    $state = $this->prophesize(ModerationStateInterface::class);
+    $state->id()->willReturn('draft');
+    $state->label()->willReturn('Draft');
+    $state->isPublishedState()->willReturn(FALSE);
+    $state->isDefaultRevisionState()->willReturn(FALSE);
+    $states['draft'] = $state->reveal();
+
+    $state = $this->prophesize(ModerationStateInterface::class);
+    $state->id()->willReturn('needs_review');
+    $state->label()->willReturn('Needs Review');
+    $state->isPublishedState()->willReturn(FALSE);
+    $state->isDefaultRevisionState()->willReturn(FALSE);
+    $states['needs_review'] = $state->reveal();
+
+    $state = $this->prophesize(ModerationStateInterface::class);
+    $state->id()->willReturn('staging');
+    $state->label()->willReturn('Staging');
+    $state->isPublishedState()->willReturn(FALSE);
+    $state->isDefaultRevisionState()->willReturn(FALSE);
+    $states['staging'] = $state->reveal();
+
+    $state = $this->prophesize(ModerationStateInterface::class);
+    $state->id()->willReturn('published');
+    $state->label()->willReturn('Published');
+    $state->isPublishedState()->willReturn(TRUE);
+    $state->isDefaultRevisionState()->willReturn(TRUE);
+    $states['published'] = $state->reveal();
+
+    $state = $this->prophesize(ModerationStateInterface::class);
+    $state->id()->willReturn('archived');
+    $state->label()->willReturn('Archived');
+    $state->isPublishedState()->willReturn(TRUE);
+    $state->isDefaultRevisionState()->willReturn(TRUE);
+    $states['archived'] = $state->reveal();
+
+    $entity_storage->loadMultiple()->willReturn($states);
+
+    foreach ($states as $id => $state) {
+      $entity_storage->load($id)->willReturn($state);
+    }
+
+    return $entity_storage->reveal();
+  }
+
+  /**
+   * Builds a mocked Entity Type Manager.
+   *
+   * @return EntityTypeManagerInterface
+   *   The mocked Entity Type Manager.
+   */
+  protected function setupEntityTypeManager(EntityStorageInterface $storage) {
+    $entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
+    $entityTypeManager->getStorage('moderation_state')->willReturn($storage);
+    $entityTypeManager->getStorage('moderation_state_transition')->willReturn($this->setupTransitionStorage());
+
+    return $entityTypeManager->reveal();
+  }
+
+  /**
+   * Builds a mocked query factory that does nothing.
+   *
+   * @return QueryFactory
+   *   The mocked query factory that does nothing.
+   */
+  protected function setupQueryFactory() {
+    $factory = $this->prophesize(QueryFactory::class);
+
+    return $factory->reveal();
+  }
+
+  /**
+   * @covers ::isTransitionAllowed
+   * @covers ::calculatePossibleTransitions
+   *
+   * @dataProvider providerIsTransitionAllowedWithValidTransition
+   */
+  public function testIsTransitionAllowedWithValidTransition($from_id, $to_id) {
+    $storage = $this->setupStateStorage();
+    $state_transition_validation = new StateTransitionValidation($this->setupEntityTypeManager($storage), $this->setupQueryFactory());
+    $this->assertTrue($state_transition_validation->isTransitionAllowed($storage->load($from_id), $storage->load($to_id)));
+  }
+
+  /**
+   * Data provider for self::testIsTransitionAllowedWithValidTransition().
+   */
+  public function providerIsTransitionAllowedWithValidTransition() {
+    return [
+      ['draft', 'draft'],
+      ['draft', 'needs_review'],
+      ['needs_review', 'needs_review'],
+      ['needs_review', 'staging'],
+      ['staging', 'published'],
+      ['needs_review', 'draft'],
+    ];
+  }
+
+  /**
+   * @covers ::isTransitionAllowed
+   * @covers ::calculatePossibleTransitions
+   *
+   * @dataProvider providerIsTransitionAllowedWithInValidTransition
+   */
+  public function testIsTransitionAllowedWithInValidTransition($from_id, $to_id) {
+    $storage = $this->setupStateStorage();
+    $state_transition_validation = new StateTransitionValidation($this->setupEntityTypeManager($storage), $this->setupQueryFactory());
+    $this->assertFalse($state_transition_validation->isTransitionAllowed($storage->load($from_id), $storage->load($to_id)));
+  }
+
+  /**
+   * Data provider for self::testIsTransitionAllowedWithInValidTransition().
+   */
+  public function providerIsTransitionAllowedWithInValidTransition() {
+    return [
+      ['published', 'needs_review'],
+      ['published', 'staging'],
+      ['staging', 'needs_review'],
+      ['staging', 'staging'],
+      ['needs_review', 'published'],
+      ['published', 'archived'],
+      ['archived', 'published'],
+    ];
+  }
+
+  /**
    * Verifies user-aware transition validation.
    *
    * @param string $from_id
@@ -30,7 +239,7 @@ class StateTransitionValidationTest extends \PHPUnit_Framework_TestCase {
    * @param bool $allowed
    *   Whether or not to grant a user this permission.
    * @param bool $result
-   *   Whether getValidTransitions() is expected to have the.
+   *   Whether userMayTransition() is expected to return TRUE or FALSE.
    *
    * @dataProvider userTransitionsProvider
    */
@@ -41,45 +250,10 @@ public function testUserSensitiveValidTransitions($from_id, $to_id, $permission,
     $user->hasPermission($permission)->willReturn($allowed);
     $user->hasPermission(Argument::type('string'))->willReturn(FALSE);
 
-    $entity = $this->prophesize(ContentEntityInterface::class);
-    $entity = $entity->reveal();
-    $entity->moderation_state = new \stdClass();
-    $entity->moderation_state->value = $from_id;
-
-    $validator = new StateTransitionValidation($this->setUpModerationInformation($entity));
-    $has_transition = FALSE;
-    foreach ($validator->getValidTransitions($entity, $user->reveal()) as $transition) {
-      if ($transition->to()->id() === $to_id) {
-        $has_transition = TRUE;
-        break;
-      }
-    }
-    $this->assertSame($result, $has_transition);
-  }
+    $storage = $this->setupStateStorage();
+    $validator = new Validator($this->setupEntityTypeManager($storage), $this->setupQueryFactory());
 
-  protected function setUpModerationInformation(ContentEntityInterface $entity) {
-    // Create a container so that the plugin manager and workflow type can be
-    // mocked.
-    $container = new ContainerBuilder();
-    $workflow_type = $this->prophesize(WorkflowTypeInterface::class);
-    $workflow_type->decorateState(Argument::any())->willReturnArgument(0);
-    $workflow_type->decorateTransition(Argument::any())->willReturnArgument(0);
-    $workflow_manager = $this->prophesize(WorkflowTypeManager::class);
-    $workflow_manager->createInstance('content_moderation', Argument::any())->willReturn($workflow_type->reveal());
-    $container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
-    \Drupal::setContainer($container);
-
-    $workflow = new Workflow(['id' => 'process', 'type' => 'content_moderation'], 'workflow');
-    $workflow
-      ->addState('draft', 'draft')
-      ->addState('needs_review', 'needs_review')
-      ->addState('published', 'published')
-      ->addTransition('draft', 'draft', ['draft'], 'draft')
-      ->addTransition('review', 'review', ['draft'], 'needs_review')
-      ->addTransition('publish', 'publish', ['needs_review', 'published'], 'published');
-    $moderation_info = $this->prophesize(ModerationInformationInterface::class);
-    $moderation_info->getWorkFlowForEntity($entity)->willReturn($workflow);
-    return $moderation_info->reveal();
+    $this->assertEquals($result, $validator->userMayTransition($storage->load($from_id), $storage->load($to_id), $user->reveal()));
   }
 
   /**
@@ -87,15 +261,37 @@ protected function setUpModerationInformation(ContentEntityInterface $entity) {
    */
   public function userTransitionsProvider() {
     // The user has the right permission, so let it through.
-    $ret[] = ['draft', 'draft', 'use process transition draft', TRUE, TRUE];
+    $ret[] = ['draft', 'draft', 'use draft__draft transition', TRUE, TRUE];
 
     // The user doesn't have the right permission, block it.
-    $ret[] = ['draft', 'draft', 'use process transition draft', FALSE, FALSE];
+    $ret[] = ['draft', 'draft', 'use draft__draft transition', FALSE, FALSE];
 
     // The user has some other permission that doesn't matter.
-    $ret[] = ['draft', 'draft', 'use process transition review', TRUE, FALSE];
+    $ret[] = ['draft', 'draft', 'use draft__needs_review transition', TRUE, FALSE];
+
+    // The user has permission, but the transition isn't allowed anyway.
+    $ret[] = ['published', 'needs_review', 'use published__needs_review transition', TRUE, FALSE];
 
     return $ret;
   }
 
 }
+
+/**
+ * Testable subclass for selected tests.
+ *
+ * EntityQuery is beyond untestable, so we have to subclass and override the
+ * method that uses it.
+ */
+class Validator extends StateTransitionValidation {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getTransitionFromStates(ModerationStateInterface $from, ModerationStateInterface $to) {
+    if ($from->id() === 'draft' && $to->id() === 'draft') {
+      return $this->transitionStorage()->loadMultiple(['draft__draft'])[0];
+    }
+  }
+
+}
diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php
index 7995125..6fd2254 100644
--- a/core/modules/content_translation/src/ContentTranslationHandler.php
+++ b/core/modules/content_translation/src/ContentTranslationHandler.php
@@ -384,16 +384,6 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
         '#multilingual' => TRUE,
       );
 
-      if (isset($form['advanced'])) {
-        $form['content_translation'] += array(
-          '#group' => 'advanced',
-          '#weight' => 100,
-          '#attributes' => array(
-            'class' => array('entity-translation-options'),
-          ),
-        );
-      }
-
       // A new translation is enabled by default.
       $metadata = $this->manager->getTranslationMetadata($entity);
       $status = $new_translation || $metadata->isPublished();
diff --git a/core/modules/dblog/dblog.info.yml b/core/modules/dblog/dblog.info.yml
index 19ef892..b214fb7 100644
--- a/core/modules/dblog/dblog.info.yml
+++ b/core/modules/dblog/dblog.info.yml
@@ -1,6 +1,6 @@
 name: 'Database Logging'
 type: module
-description: 'Logs system events in the database.'
+description: 'Logs and records system events to the database.'
 package: Core
 version: VERSION
 core: 8.x
diff --git a/core/modules/editor/editor.info.yml b/core/modules/editor/editor.info.yml
index cdf5ee9..0c8ff38 100644
--- a/core/modules/editor/editor.info.yml
+++ b/core/modules/editor/editor.info.yml
@@ -1,6 +1,6 @@
 name: 'Text Editor'
 type: module
-description: 'Provides a framework to associate text editors and toolbars with text formats.'
+description: 'Provides a means to associate text formats with text editor libraries such as WYSIWYGs or toolbars.'
 package: Core
 version: VERSION
 core: 8.x
diff --git a/core/modules/field/migration_templates/d7_field_instance.yml b/core/modules/field/migration_templates/d7_field_instance.yml
index da4ce0a..6f9d074 100644
--- a/core/modules/field/migration_templates/d7_field_instance.yml
+++ b/core/modules/field/migration_templates/d7_field_instance.yml
@@ -36,4 +36,3 @@ migration_dependencies:
   optional:
     - d7_node_type
     - d7_comment_type
-    - d7_taxonomy_vocabulary
diff --git a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
index d0f7474..b2386b4 100644
--- a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
+++ b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
@@ -292,21 +292,6 @@ public function massageFormValues(array $values, array $form, FormStateInterface
   }
 
   /**
-   * {@inheritdoc}
-   */
-  public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
-    parent::extractFormValues($items, $form, $form_state);
-
-    // Update reference to 'items' stored during upload to take into account
-    // changes to values like 'alt' etc.
-    // @see \Drupal\file\Plugin\Field\FieldWidget\FileWidget::submit()
-    $field_name = $this->fieldDefinition->getName();
-    $field_state = static::getWidgetState($form['#parents'], $field_name, $form_state);
-    $field_state['items'] = $items->getValue();
-    static::setWidgetState($form['#parents'], $field_name, $form_state, $field_state);
-  }
-
-  /**
    * Form API callback. Retrieves the value for the file_generic field element.
    *
    * This method is assigned as a #value_callback in formElement() method.
diff --git a/core/modules/filter/src/FilterProcessResult.php b/core/modules/filter/src/FilterProcessResult.php
index 3fa592b..4b07fad 100644
--- a/core/modules/filter/src/FilterProcessResult.php
+++ b/core/modules/filter/src/FilterProcessResult.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\filter;
 
-use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Render\BubbleableMetadata;
@@ -135,7 +134,7 @@ public function createPlaceholder($callback, array $args) {
     // Generate placeholder markup.
     // @see \Drupal\Core\Render\PlaceholderGenerator::createPlaceholder()
     $arguments = UrlHelper::buildQuery($args);
-    $token = Crypt::hashBase64(serialize([$callback, $args]));
+    $token = hash('crc32b', serialize([$callback, $args]));
     $placeholder_markup = '<drupal-filter-placeholder callback="' . Html::escape($callback) . '" arguments="' . Html::escape($arguments) . '" token="' . Html::escape($token) . '"></drupal-filter-placeholder>';
 
     // Add the placeholder attachment.
diff --git a/core/modules/inline_form_errors/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php b/core/modules/inline_form_errors/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php
deleted file mode 100644
index 3ee24b4..0000000
--- a/core/modules/inline_form_errors/tests/src/FunctionalJavascript/FormErrorHandlerCKEditorTest.php
+++ /dev/null
@@ -1,111 +0,0 @@
-<?php
-
-namespace Drupal\Tests\inline_form_errors\FunctionalJavascript;
-
-use Drupal\Core\Entity\Entity\EntityFormDisplay;
-use Drupal\editor\Entity\Editor;
-use Drupal\field\Entity\FieldConfig;
-use Drupal\field\Entity\FieldStorageConfig;
-use Drupal\filter\Entity\FilterFormat;
-use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
-use Drupal\node\Entity\NodeType;
-
-/**
- * Tests the inline errors fragment link to a CKEditor-enabled textarea.
- *
- * @group ckeditor
- */
-class FormErrorHandlerCKEditorTest extends JavascriptTestBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public static $modules = ['node', 'ckeditor', 'inline_form_errors', 'filter'];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-
-    // Create a text format and associate CKEditor.
-    $filtered_html_format = FilterFormat::create([
-      'format' => 'filtered_html',
-      'name' => 'Filtered HTML',
-      'weight' => 0,
-    ]);
-    $filtered_html_format->save();
-
-    Editor::create([
-      'format' => 'filtered_html',
-      'editor' => 'ckeditor',
-    ])->save();
-
-    // Create a node type for testing.
-    NodeType::create(['type' => 'page', 'name' => 'page'])->save();
-
-    $field_storage = FieldStorageConfig::loadByName('node', 'body');
-
-    // Create a body field instance for the 'page' node type.
-    FieldConfig::create([
-      'field_storage' => $field_storage,
-      'bundle' => 'page',
-      'label' => 'Body',
-      'settings' => ['display_summary' => TRUE],
-      'required' => TRUE,
-    ])->save();
-
-    // Assign widget settings for the 'default' form mode.
-    EntityFormDisplay::create([
-      'targetEntityType' => 'node',
-      'bundle' => 'page',
-      'mode' => 'default',
-      'status' => TRUE,
-    ])->setComponent('body', ['type' => 'text_textarea_with_summary'])
-      ->save();
-
-    $account = $this->drupalCreateUser([
-      'administer nodes',
-      'create page content',
-      'use text format filtered_html',
-    ]);
-    $this->drupalLogin($account);
-  }
-
-  /**
-   * Tests if the fragment link to a textarea works with CKEditor enabled.
-   */
-  public function testFragmentLink() {
-    $session = $this->getSession();
-    $web_assert = $this->assertSession();
-    $ckeditor_id = '#cke_edit-body-0-value';
-
-    $this->drupalGet('node/add/page');
-
-    $page = $this->getSession()->getPage();
-
-    // Only enter a title in the node add form and leave the body field empty.
-    $edit = ['edit-title-0-value' => 'Test inline form error with CKEditor'];
-
-    $this->submitForm($edit, 'Save and publish');
-
-    // Add a bottom margin to the title field to be sure the body field is not
-    // visible. PhantomJS runs with a resolution of 1024x768px.
-    $session->executeScript("document.getElementById('edit-title-0-value').style.marginBottom = '800px';");
-
-    // Check that the CKEditor-enabled body field is currently not visible in
-    // the viewport.
-    $web_assert->assertNotVisibleInViewport('css', $ckeditor_id, 'topLeft', 'CKEditor-enabled body field is not visible.');
-
-    // Check if we can find the error fragment link within the errors summary
-    // message.
-    $errors_link = $page->find('css', '.messages--error a[href=\#edit-body-0-value]');
-    $this->assertTrue($errors_link->isVisible(), 'Error fragment link is visible.');
-
-    $errors_link->click();
-
-    // Check that the CKEditor-enabled body field is visible in the viewport.
-    $web_assert->assertVisibleInViewport('css', $ckeditor_id, 'topLeft', 'CKEditor-enabled body field is visible.');
-  }
-
-}
diff --git a/core/modules/link/link.info.yml b/core/modules/link/link.info.yml
index 36f21b1..509d2ba 100644
--- a/core/modules/link/link.info.yml
+++ b/core/modules/link/link.info.yml
@@ -1,6 +1,6 @@
 name: Link
 type: module
-description: 'Defines a field type that contain internal or external URLs.'
+description: 'Provides a simple link field type.'
 core: 8.x
 package: Field types
 version: VERSION
diff --git a/core/modules/menu_link_content/migration_templates/d7_menu_links.yml b/core/modules/menu_link_content/migration_templates/d7_menu_links.yml
index 7f049f7..8581e2c 100644
--- a/core/modules/menu_link_content/migration_templates/d7_menu_links.yml
+++ b/core/modules/menu_link_content/migration_templates/d7_menu_links.yml
@@ -20,7 +20,7 @@ process:
       plugin: skip_on_empty
       method: row
   'link/uri':
-    plugin: link_uri
+    plugin: d7_internal_uri
     source:
       - link_path
   'link/options': options
diff --git a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
index 8a708ce..ed372a9 100644
--- a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
+++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
@@ -2,10 +2,8 @@
 
 namespace Drupal\menu_link_content\Form;
 
-use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\Entity\ContentEntityForm;
 use Drupal\Core\Entity\EntityManagerInterface;
-use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Menu\MenuParentFormSelectorInterface;
@@ -49,13 +47,9 @@ class MenuLinkContentForm extends ContentEntityForm {
    *   The language manager.
    * @param \Drupal\Core\Path\PathValidatorInterface $path_validator
    *   The path validator.
-   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
-   *   The entity type bundle service.
-   * @param \Drupal\Component\Datetime\TimeInterface $time
-   *   The time service.
    */
-  public function __construct(EntityManagerInterface $entity_manager, MenuParentFormSelectorInterface $menu_parent_selector, LanguageManagerInterface $language_manager, PathValidatorInterface $path_validator, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
-    parent::__construct($entity_manager, $entity_type_bundle_info, $time);
+  public function __construct(EntityManagerInterface $entity_manager, MenuParentFormSelectorInterface $menu_parent_selector, LanguageManagerInterface $language_manager, PathValidatorInterface $path_validator) {
+    parent::__construct($entity_manager, $language_manager);
     $this->menuParentSelector = $menu_parent_selector;
     $this->pathValidator = $path_validator;
   }
@@ -68,9 +62,7 @@ public static function create(ContainerInterface $container) {
       $container->get('entity.manager'),
       $container->get('menu.parent_form_selector'),
       $container->get('language_manager'),
-      $container->get('path.validator'),
-      $container->get('entity_type.bundle.info'),
-      $container->get('datetime.time')
+      $container->get('path.validator')
     );
   }
 
diff --git a/core/modules/menu_link_content/src/Plugin/migrate/process/LinkUri.php b/core/modules/menu_link_content/src/Plugin/migrate/process/LinkUri.php
index 839b639..93656c8 100644
--- a/core/modules/menu_link_content/src/Plugin/migrate/process/LinkUri.php
+++ b/core/modules/menu_link_content/src/Plugin/migrate/process/LinkUri.php
@@ -63,9 +63,6 @@ public function transform($value, MigrateExecutableInterface $migrate_executable
     $path = ltrim($path, '/');
 
     if (parse_url($path, PHP_URL_SCHEME) === NULL) {
-      if ($path == '<front>') {
-        $path = '';
-      }
       $path = 'internal:/' . $path;
 
       // Convert entity URIs to the entity scheme, if the path matches a route
diff --git a/core/modules/menu_link_content/src/Plugin/migrate/process/d7/InternalUri.php b/core/modules/menu_link_content/src/Plugin/migrate/process/d7/InternalUri.php
index fc63675..9d37356 100644
--- a/core/modules/menu_link_content/src/Plugin/migrate/process/d7/InternalUri.php
+++ b/core/modules/menu_link_content/src/Plugin/migrate/process/d7/InternalUri.php
@@ -2,12 +2,42 @@
 
 namespace Drupal\menu_link_content\Plugin\migrate\process\d7;
 
-use Drupal\menu_link_content\Plugin\migrate\process\LinkUri;
+use Drupal\migrate\MigrateExecutableInterface;
+use Drupal\migrate\ProcessPluginBase;
+use Drupal\migrate\Row;
 
 /**
- * Processes an internal uri into an 'internal:' or 'entity:' URI.
+ * Process a path into an 'internal:' URI.
  *
- * @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use
- * \Drupal\menu_link_content\Plugin\migrate\process\LinkUri instead.
+ * @MigrateProcessPlugin(
+ *   id = "d7_internal_uri"
+ * )
  */
-class InternalUri extends LinkUri {}
+class InternalUri extends ProcessPluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
+    list($path) = $value;
+    $path = ltrim($path, '/');
+
+    if (parse_url($path, PHP_URL_SCHEME) == NULL) {
+      // If $path is the node page (i.e. node/[nid]) then return entity path.
+      if (preg_match('/^node\/\d+$/', $path)) {
+        // "entity: URI"s enable the menu link to appear in the Menu Settings
+        // section on the node edit page. Other entities (e.g. taxonomy terms,
+        // users) do not have the Menu Settings section.
+        return 'entity:' . $path;
+      }
+      elseif ($path == '<front>') {
+        return 'internal:/';
+      }
+      else {
+        return 'internal:/' . $path;
+      }
+    }
+    return $path;
+  }
+
+}
diff --git a/core/modules/menu_link_content/tests/src/Kernel/Migrate/d7/MigrateMenuLinkTest.php b/core/modules/menu_link_content/tests/src/Kernel/Migrate/d7/MigrateMenuLinkTest.php
index 1d757a5..0c915d4 100644
--- a/core/modules/menu_link_content/tests/src/Kernel/Migrate/d7/MigrateMenuLinkTest.php
+++ b/core/modules/menu_link_content/tests/src/Kernel/Migrate/d7/MigrateMenuLinkTest.php
@@ -5,7 +5,6 @@
 use Drupal\Core\Menu\MenuTreeParameters;
 use Drupal\menu_link_content\Entity\MenuLinkContent;
 use Drupal\menu_link_content\MenuLinkContentInterface;
-use Drupal\node\Entity\Node;
 use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
 
 /**
@@ -19,7 +18,7 @@ class MigrateMenuLinkTest extends MigrateDrupal7TestBase {
   /**
    * {@inheritdoc}
    */
-  public static $modules = array('link', 'menu_ui', 'menu_link_content', 'node');
+  public static $modules = array('link', 'menu_ui', 'menu_link_content');
 
   /**
    * {@inheritdoc}
@@ -27,13 +26,6 @@ class MigrateMenuLinkTest extends MigrateDrupal7TestBase {
   protected function setUp() {
     parent::setUp();
     $this->installEntitySchema('menu_link_content');
-    $this->installEntitySchema('node');
-    $node = Node::create([
-      'nid' => 3,
-      'title' => 'node link test',
-      'type' => 'article',
-    ]);
-    $node->save();
     $this->executeMigrations(['d7_menu', 'd7_menu_links']);
     \Drupal::service('router.builder')->rebuild();
   }
diff --git a/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/process/LinkUriTest.php b/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/process/LinkUriTest.php
index d647612..073180f 100644
--- a/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/process/LinkUriTest.php
+++ b/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/process/LinkUriTest.php
@@ -117,10 +117,6 @@ public function providerTestTransform() {
     $expected = 'internal:/test';
     $tests['without_scheme'] = [$value, $expected];
 
-    $value = ['<front>'];
-    $expected = 'internal:/';
-    $tests['front'] = [$value, $expected];
-
     $url = Url::fromRoute('route_name');
     $tests['with_route'] = [$value, $expected, $url];
 
diff --git a/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/process/d7/InternalUriTest.php b/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/process/d7/InternalUriTest.php
new file mode 100644
index 0000000..9292718
--- /dev/null
+++ b/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/process/d7/InternalUriTest.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Drupal\Tests\menu_link_content\Unit\Plugin\migrate\process\d7;
+
+use Drupal\menu_link_content\Plugin\migrate\process\d7\InternalUri;
+use Drupal\migrate\MigrateExecutableInterface;
+use Drupal\migrate\Row;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests \Drupal\menu_link_content\Plugin\migrate\process\d7\InternalUri.
+ *
+ * @group menu_link_content
+ *
+ * @coversDefaultClass \Drupal\menu_link_content\Plugin\migrate\process\d7\InternalUri
+ */
+class InternalUriTest extends UnitTestCase {
+
+  /**
+   * The 'd7_internal_uri' process plugin being tested.
+   *
+   * @var \Drupal\menu_link_content\Plugin\migrate\process\d7\InternalUri
+   */
+  protected $processPlugin;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->processPlugin = new InternalUri([], 'd7_internal_uri', []);
+  }
+
+  /**
+   * Tests InternalUri::transform().
+   *
+   * @param array $value
+   *   The value to pass to InternalUri::transform().
+   * @param string $expected
+   *   The expected return value of InternalUri::transform().
+   *
+   * @dataProvider providerTestTransform
+   *
+   * @covers ::transform
+   */
+  public function testTransform(array $value, $expected) {
+    $migrate_executable = $this->prophesize(MigrateExecutableInterface::class);
+    $row = $this->prophesize(Row::class);
+
+    $actual = $this->processPlugin->transform($value, $migrate_executable->reveal(), $row->reveal(), 'link/uri');
+    $this->assertEquals($expected, $actual);
+  }
+
+  /**
+   * Provides test cases for InternalUriTest::testTransform().
+   *
+   * @return array
+   *   An array of test cases, each which the following values:
+   *   - The value array to pass to InternalUri::transform().
+   *   - The expected path returned by InternalUri::transform().
+   */
+  public function providerTestTransform() {
+    $tests = [];
+    $tests['with_scheme'] = [['http://example.com'], 'http://example.com'];
+    $tests['leading_slash'] = [['/test'], 'internal:/test'];
+    $tests['without_scheme'] = [['test'], 'internal:/test'];
+    $tests['front'] = [['<front>'], 'internal:/'];
+    $tests['node'] = [['node/27'], 'entity:node/27'];
+    return $tests;
+  }
+
+}
diff --git a/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php b/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php
index 9aa5900..c68fe34 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/FileCopy.php
@@ -112,17 +112,20 @@ public function transform($value, MigrateExecutableInterface $migrate_executable
       return $destination;
     }
 
-    // Check if a writable directory exists, and if not try to create it.
+    $replace = $this->getOverwriteMode();
+    // We attempt the copy/move first to avoid calling file_prepare_directory()
+    // any more than absolutely necessary.
+    $final_destination = $this->writeFile($source, $destination, $replace);
+    if ($final_destination) {
+      return $final_destination;
+    }
+    // If writeFile didn't work, make sure there's a writable directory in
+    // place.
     $dir = $this->getDirectory($destination);
-    // If the directory exists and is writable, avoid file_prepare_directory()
-    // call and write the file to destination.
-    if (!is_dir($dir) || !is_writable($dir)) {
-      if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
-        throw new MigrateException("Could not create or write to directory '$dir'");
-      }
+    if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+      throw new MigrateException("Could not create or write to directory '$dir'");
     }
-
-    $final_destination = $this->writeFile($source, $destination, $this->getOverwriteMode());
+    $final_destination = $this->writeFile($source, $destination, $replace);
     if ($final_destination) {
       return $final_destination;
     }
diff --git a/core/modules/migrate/src/Plugin/migrate/process/Iterator.php b/core/modules/migrate/src/Plugin/migrate/process/Iterator.php
index 2a41c4a..70df9ba 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/Iterator.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/Iterator.php
@@ -22,17 +22,15 @@ class Iterator extends ProcessPluginBase {
    * Runs a process pipeline on each destination property per list item.
    */
   public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
-    $return = [];
-    if (!is_null($value)) {
-      foreach ($value as $key => $new_value) {
-        $new_row = new Row($new_value, []);
-        $migrate_executable->processRow($new_row, $this->configuration['process']);
-        $destination = $new_row->getDestination();
-        if (array_key_exists('key', $this->configuration)) {
-          $key = $this->transformKey($key, $migrate_executable, $new_row);
-        }
-        $return[$key] = $destination;
+    $return = array();
+    foreach ($value as $key => $new_value) {
+      $new_row = new Row($new_value, array());
+      $migrate_executable->processRow($new_row, $this->configuration['process']);
+      $destination = $new_row->getDestination();
+      if (array_key_exists('key', $this->configuration)) {
+        $key = $this->transformKey($key, $migrate_executable, $new_row);
       }
+      $return[$key] = $destination;
     }
     return $return;
   }
diff --git a/core/modules/migrate/tests/src/Kernel/process/FileCopyTest.php b/core/modules/migrate/tests/src/Kernel/process/FileCopyTest.php
index 451d832..ee75e45 100644
--- a/core/modules/migrate/tests/src/Kernel/process/FileCopyTest.php
+++ b/core/modules/migrate/tests/src/Kernel/process/FileCopyTest.php
@@ -4,7 +4,6 @@
 
 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
 use Drupal\KernelTests\Core\File\FileTestBase;
-use Drupal\migrate\MigrateException;
 use Drupal\migrate\Plugin\migrate\process\FileCopy;
 use Drupal\migrate\MigrateExecutableInterface;
 use Drupal\migrate\Plugin\MigrateProcessInterface;
@@ -13,8 +12,6 @@
 /**
  * Tests the file_copy process plugin.
  *
- * @coversDefaultClass \Drupal\migrate\Plugin\migrate\process\FileCopy
- *
  * @group migrate
  */
 class FileCopyTest extends FileTestBase {
@@ -124,32 +121,6 @@ public function testNonExistentSourceFile() {
   }
 
   /**
-   * Tests that non-writable destination throw an exception.
-   *
-   * @covers ::transform
-   */
-  public function testNonWritableDestination() {
-    $source = $this->createUri('file.txt', NULL, 'temporary');
-
-    // Create the parent location.
-    $this->createDirectory('public://dir');
-
-    // Copy the file under public://dir/subdir1/.
-    $this->doTransform($source, 'public://dir/subdir1/file.txt');
-
-    // Check that 'subdir1' was created and the file was successfully migrated.
-    $this->assertFileExists('public://dir/subdir1/file.txt');
-
-    // Remove all permissions from public://dir to trigger a failure when
-    // trying to create a subdirectory 'subdir2' inside public://dir.
-    $this->fileSystem->chmod('public://dir', 0);
-
-    // Check that the proper exception is raised.
-    $this->setExpectedException(MigrateException::class, "Could not create or write to directory 'public://dir/subdir2'");
-    $this->doTransform($source, 'public://dir/subdir2/file.txt');
-  }
-
-  /**
    * Test the 'rename' overwrite mode.
    */
   public function testRenameFile() {
diff --git a/core/modules/migrate_drupal/tests/fixtures/drupal7.php b/core/modules/migrate_drupal/tests/fixtures/drupal7.php
index dbe6c8e..6853e1e 100644
--- a/core/modules/migrate_drupal/tests/fixtures/drupal7.php
+++ b/core/modules/migrate_drupal/tests/fixtures/drupal7.php
@@ -3828,15 +3828,6 @@
   'data' => 'a:7:{s:5:"label";s:21:"Term Entity Reference";s:6:"widget";a:5:{s:6:"weight";s:2:"18";s:4:"type";s:33:"entityreference_autocomplete_tags";s:6:"module";s:15:"entityreference";s:6:"active";i:1;s:8:"settings";a:3:{s:14:"match_operator";s:8:"CONTAINS";s:4:"size";s:2:"60";s:4:"path";s:0:"";}}s:8:"settings";a:2:{s:9:"behaviors";a:1:{s:14:"taxonomy-index";a:1:{s:6:"status";b:1;}}s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:21:"entityreference_label";s:8:"settings";a:1:{s:4:"link";b:0;}s:6:"module";s:15:"entityreference";s:6:"weight";i:17;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}',
   'deleted' => '0',
 ))
-->values(array(
-  'id' => '41',
-  'field_id' => '20',
-  'field_name' => 'field_term_reference',
-  'entity_type' => 'taxonomy_term',
-  'bundle' => 'test_vocabulary',
-  'data' => 'a:7:{s:5:"label";s:14:"Term Reference";s:6:"widget";a:5:{s:6:"weight";s:2:"14";s:4:"type";s:21:"taxonomy_autocomplete";s:6:"module";s:8:"taxonomy";s:6:"active";i:0;s:8:"settings";a:2:{s:4:"size";i:60;s:17:"autocomplete_path";s:21:"taxonomy/autocomplete";}}s:8:"settings";a:1:{s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:4:{s:5:"label";s:5:"above";s:4:"type";s:6:"hidden";s:6:"weight";s:2:"13";s:8:"settings";a:0:{}}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}',
-  'deleted' => '0',
-))
 ->execute();
 
 $connection->schema()->createTable('field_data_body', array(
@@ -4859,16 +4850,6 @@
   'delta' => '0',
   'field_integer_value' => '99',
 ))
-->values(array(
-  'entity_type' => 'taxonomy_term',
-  'bundle' => 'test_vocabulary',
-  'deleted' => '0',
-  'entity_id' => '4',
-  'revision_id' => '4',
-  'language' => 'und',
-  'delta' => '0',
-  'field_integer_value' => '6',
-))
 ->execute();
 
 $connection->schema()->createTable('field_data_field_integer_list', array(
@@ -5668,16 +5649,6 @@
   'delta' => '0',
   'field_term_reference_tid' => '4',
 ))
-->values(array(
-  'entity_type' => 'taxonomy_term',
-  'bundle' => 'test_vocabulary',
-  'deleted' => '0',
-  'entity_id' => '2',
-  'revision_id' => '2',
-  'language' => 'und',
-  'delta' => '0',
-  'field_term_reference_tid' => '3',
-))
 ->execute();
 
 $connection->schema()->createTable('field_data_field_text', array(
@@ -7052,16 +7023,6 @@
   'delta' => '0',
   'field_integer_value' => '99',
 ))
-->values(array(
-  'entity_type' => 'taxonomy_term',
-  'bundle' => 'test_vocabulary',
-  'deleted' => '0',
-  'entity_id' => '4',
-  'revision_id' => '4',
-  'language' => 'und',
-  'delta' => '0',
-  'field_integer_value' => '6',
-))
 ->execute();
 
 $connection->schema()->createTable('field_revision_field_integer_list', array(
@@ -7869,16 +7830,6 @@
   'delta' => '0',
   'field_term_reference_tid' => '4',
 ))
-->values(array(
-  'entity_type' => 'taxonomy_term',
-  'bundle' => 'test_vocabulary',
-  'deleted' => '0',
-  'entity_id' => '2',
-  'revision_id' => '2',
-  'language' => 'und',
-  'delta' => '0',
-  'field_term_reference_tid' => '3',
-))
 ->execute();
 
 $connection->schema()->createTable('field_revision_field_text', array(
diff --git a/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php b/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php
index 1a0e334..d693085 100644
--- a/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php
+++ b/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php
@@ -43,8 +43,8 @@ protected function getEntityCounts() {
       'configurable_language' => 4,
       'contact_form' => 3,
       'editor' => 2,
-      'field_config' => 49,
-      'field_storage_config' => 37,
+      'field_config' => 48,
+      'field_storage_config' => 36,
       'file' => 1,
       'filter_format' => 7,
       'image_style' => 6,
diff --git a/core/modules/node/node.js b/core/modules/node/node.js
index 086263d..98af6fd 100644
--- a/core/modules/node/node.js
+++ b/core/modules/node/node.js
@@ -18,6 +18,21 @@
   Drupal.behaviors.nodeDetailsSummaries = {
     attach: function (context) {
       var $context = $(context);
+      $context.find('.node-form-revision-information').drupalSetSummary(function (context) {
+        var $revisionContext = $(context);
+        var revisionCheckbox = $revisionContext.find('.js-form-item-revision input');
+
+          // Return 'New revision' if the 'Create new revision' checkbox is
+          // checked, or if the checkbox doesn't exist, but the revision log does.
+          // For users without the "Administer content" permission the checkbox
+          // won't appear, but the revision log will if the content type is set to
+          // auto-revision.
+        if (revisionCheckbox.is(':checked') || (!revisionCheckbox.length && $revisionContext.find('.js-form-item-revision-log textarea').length)) {
+          return Drupal.t('New revision');
+        }
+
+        return Drupal.t('No revision');
+      });
 
       $context.find('.node-form-author').drupalSetSummary(function (context) {
         var $authorContext = $(context);
@@ -49,6 +64,22 @@
           return Drupal.t('Not promoted');
         }
       });
+
+      $context.find('fieldset.node-translation-options').drupalSetSummary(function (context) {
+        var $translationContext = $(context);
+        var translate;
+        var $checkbox = $translationContext.find('.js-form-item-translation-translate input');
+
+        if ($checkbox.size()) {
+          translate = $checkbox.is(':checked') ? Drupal.t('Needs to be updated') : Drupal.t('Does not need to be updated');
+        }
+        else {
+          $checkbox = $translationContext.find('.js-form-item-translation-retranslate input');
+          translate = $checkbox.is(':checked') ? Drupal.t('Flag other translations as outdated') : Drupal.t('Do not flag other translations as outdated');
+        }
+
+        return translate;
+      });
     }
   };
 
diff --git a/core/modules/node/node.libraries.yml b/core/modules/node/node.libraries.yml
index 59947a2..22c93ac 100644
--- a/core/modules/node/node.libraries.yml
+++ b/core/modules/node/node.libraries.yml
@@ -6,8 +6,10 @@ drupal.node:
   js:
     node.js: {}
   dependencies:
-    - core/drupal.entity-form
+    - core/jquery
+    - core/drupal
     - core/drupalSettings
+    - core/drupal.form
 
 drupal.node.preview:
   version: VERSION
diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php
index 68afc83..f034957 100644
--- a/core/modules/node/src/Entity/Node.php
+++ b/core/modules/node/src/Entity/Node.php
@@ -46,7 +46,6 @@
  *   data_table = "node_field_data",
  *   revision_table = "node_revision",
  *   revision_data_table = "node_field_revision",
- *   show_revision_ui = TRUE,
  *   translatable = TRUE,
  *   list_cache_contexts = { "user.node_grants:view" },
  *   entity_keys = {
diff --git a/core/modules/node/src/Entity/NodeType.php b/core/modules/node/src/Entity/NodeType.php
index 23f24a4..91d8a90 100644
--- a/core/modules/node/src/Entity/NodeType.php
+++ b/core/modules/node/src/Entity/NodeType.php
@@ -205,11 +205,4 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti
     $storage->resetCache(array_keys($entities));
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function shouldCreateNewRevision() {
-    return $this->isNewRevision();
-  }
-
 }
diff --git a/core/modules/node/src/Form/NodePreviewForm.php b/core/modules/node/src/Form/NodePreviewForm.php
index 8a4cce9..4027fdb 100644
--- a/core/modules/node/src/Form/NodePreviewForm.php
+++ b/core/modules/node/src/Form/NodePreviewForm.php
@@ -72,7 +72,7 @@ public function getFormId() {
   public function buildForm(array $form, FormStateInterface $form_state, EntityInterface $node = NULL) {
     $view_mode = $node->preview_view_mode;
 
-    $query_options = array('query' => array('uuid' => $node->uuid()));
+    $query_options = $node->isNew() ? array('query' => array('uuid' => $node->uuid())) : array();
     $form['backlink'] = array(
       '#type' => 'link',
       '#title' => $this->t('Back to content editing'),
diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/NodeForm.php
index 3a9ed54..2828030 100644
--- a/core/modules/node/src/NodeForm.php
+++ b/core/modules/node/src/NodeForm.php
@@ -2,10 +2,8 @@
 
 namespace Drupal\node;
 
-use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\Entity\ContentEntityForm;
 use Drupal\Core\Entity\EntityManagerInterface;
-use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\user\PrivateTempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -28,19 +26,15 @@ class NodeForm extends ContentEntityForm {
   protected $hasBeenPreviewed = FALSE;
 
   /**
-   * Constructs a NodeForm object.
+   * Constructs a ContentEntityForm object.
    *
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
    * @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
    *   The factory for the temp store object.
-   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
-   *   The entity type bundle service.
-   * @param \Drupal\Component\Datetime\TimeInterface $time
-   *   The time service.
    */
-  public function __construct(EntityManagerInterface $entity_manager, PrivateTempStoreFactory $temp_store_factory, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
-    parent::__construct($entity_manager, $entity_type_bundle_info, $time);
+  public function __construct(EntityManagerInterface $entity_manager, PrivateTempStoreFactory $temp_store_factory) {
+    parent::__construct($entity_manager);
     $this->tempStoreFactory = $temp_store_factory;
   }
 
@@ -50,40 +44,54 @@ public function __construct(EntityManagerInterface $entity_manager, PrivateTempS
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('entity.manager'),
-      $container->get('user.private_tempstore'),
-      $container->get('entity_type.bundle.info'),
-      $container->get('datetime.time')
+      $container->get('user.private_tempstore')
     );
   }
 
   /**
    * {@inheritdoc}
    */
+  protected function prepareEntity() {
+    /** @var \Drupal\node\NodeInterface $node */
+    $node = $this->entity;
+
+    if (!$node->isNew()) {
+      // Remove the revision log message from the original node entity.
+      $node->revision_log = NULL;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function form(array $form, FormStateInterface $form_state) {
     // Try to restore from temp store, this must be done before calling
     // parent::form().
+    $uuid = $this->entity->uuid();
     $store = $this->tempStoreFactory->get('node_preview');
 
-    // Attempt to load from preview when the uuid is present unless we are
-    // rebuilding the form.
-    $request_uuid = \Drupal::request()->query->get('uuid');
-    if (!$form_state->isRebuilding() && $request_uuid && $preview = $store->get($request_uuid)) {
+    // If the user is creating a new node, the UUID is passed in the request.
+    if ($request_uuid = \Drupal::request()->query->get('uuid')) {
+      $uuid = $request_uuid;
+    }
+
+    if ($preview = $store->get($uuid)) {
       /** @var $preview \Drupal\Core\Form\FormStateInterface */
 
-      $form_state->setStorage($preview->getStorage());
-      $form_state->setUserInput($preview->getUserInput());
+      foreach ($preview->getValues() as $name => $value) {
+        $form_state->setValue($name, $value);
+      }
 
       // Rebuild the form.
       $form_state->setRebuild();
-
-      // The combination of having user input and rebuilding the form means
-      // that it will attempt to cache the form state which will fail if it is
-      // a GET request.
-      $form_state->setRequestMethod('POST');
-
       $this->entity = $preview->getFormObject()->getEntity();
       $this->entity->in_preview = NULL;
 
+      // Remove the stale temp store entry for existing nodes.
+      if (!$this->entity->isNew()) {
+        $store->delete($uuid);
+      }
+
       $this->hasBeenPreviewed = TRUE;
     }
 
@@ -94,15 +102,55 @@ public function form(array $form, FormStateInterface $form_state) {
       $form['#title'] = $this->t('<em>Edit @type</em> @title', array('@type' => node_get_type_label($node), '@title' => $node->label()));
     }
 
+    $current_user = $this->currentUser();
+
     // Changed must be sent to the client, for later overwrite error checking.
     $form['changed'] = array(
       '#type' => 'hidden',
       '#default_value' => $node->getChangedTime(),
     );
 
+    $form['advanced'] = array(
+      '#type' => 'vertical_tabs',
+      '#attributes' => array('class' => array('entity-meta')),
+      '#weight' => 99,
+    );
     $form = parent::form($form, $form_state);
 
-    $form['advanced']['#attributes']['class'][] = 'entity-meta';
+    // Add a revision_log field if the "Create new revision" option is checked,
+    // or if the current user has the ability to check that option.
+    $form['revision_information'] = array(
+      '#type' => 'details',
+      '#group' => 'advanced',
+      '#title' => t('Revision information'),
+      // Open by default when "Create new revision" is checked.
+      '#open' => $node->isNewRevision(),
+      '#attributes' => array(
+        'class' => array('node-form-revision-information'),
+      ),
+      '#attached' => array(
+        'library' => array('node/drupal.node'),
+      ),
+      '#weight' => 20,
+      '#optional' => TRUE,
+    );
+
+    $form['revision'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Create new revision'),
+      '#default_value' => $node->type->entity->isNewRevision(),
+      '#access' => $current_user->hasPermission('administer nodes') && !$node->isNew(),
+      '#group' => 'revision_information',
+    );
+
+    $form['revision_log'] += array(
+      '#states' => array(
+        'visible' => array(
+          ':input[name="revision"]' => array('checked' => TRUE),
+        ),
+      ),
+      '#group' => 'revision_information',
+    );
 
     // Node author information for administrators.
     $form['author'] = array(
@@ -256,6 +304,32 @@ protected function actions(array $form, FormStateInterface $form_state) {
   }
 
   /**
+   * {@inheritdoc}
+   *
+   * Updates the node object by processing the submitted values.
+   *
+   * This function can be called by a "Next" button of a wizard to update the
+   * form state's entity with the current step's values before proceeding to the
+   * next step.
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Build the node object from the submitted values.
+    parent::submitForm($form, $form_state);
+    $node = $this->entity;
+
+    // Save as a new revision if requested to do so.
+    if (!$form_state->isValueEmpty('revision') && $form_state->getValue('revision') != FALSE) {
+      $node->setNewRevision();
+      // If a new revision is created, save the current user as revision author.
+      $node->setRevisionCreationTime(REQUEST_TIME);
+      $node->setRevisionUserId(\Drupal::currentUser()->id());
+    }
+    else {
+      $node->setNewRevision(FALSE);
+    }
+  }
+
+  /**
    * Form submission handler for the 'preview' action.
    *
    * @param $form
diff --git a/core/modules/node/src/NodeTranslationHandler.php b/core/modules/node/src/NodeTranslationHandler.php
index 2f6d2ab..9c9741e 100644
--- a/core/modules/node/src/NodeTranslationHandler.php
+++ b/core/modules/node/src/NodeTranslationHandler.php
@@ -17,7 +17,17 @@ class NodeTranslationHandler extends ContentTranslationHandler {
   public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
     parent::entityFormAlter($form, $form_state, $entity);
 
+    // Move the translation fieldset to a vertical tab.
     if (isset($form['content_translation'])) {
+      $form['content_translation'] += array(
+        '#group' => 'advanced',
+        '#attributes' => array(
+          'class' => array('node-translation-options'),
+        ),
+      );
+
+      $form['content_translation']['#weight'] = 100;
+
       // We do not need to show these values on node forms: they inherit the
       // basic node property values.
       $form['content_translation']['status']['#access'] = FALSE;
diff --git a/core/modules/node/src/NodeTypeInterface.php b/core/modules/node/src/NodeTypeInterface.php
index df4831e..c034ffb 100644
--- a/core/modules/node/src/NodeTypeInterface.php
+++ b/core/modules/node/src/NodeTypeInterface.php
@@ -3,12 +3,11 @@
 namespace Drupal\node;
 
 use Drupal\Core\Config\Entity\ConfigEntityInterface;
-use Drupal\Core\Entity\RevisionableEntityBundleInterface;
 
 /**
  * Provides an interface defining a node type entity.
  */
-interface NodeTypeInterface extends ConfigEntityInterface, RevisionableEntityBundleInterface {
+interface NodeTypeInterface extends ConfigEntityInterface {
 
   /**
    * Determines whether the node type is locked.
@@ -23,17 +22,13 @@ public function isLocked();
    *
    * @return bool
    *   TRUE if a new revision should be created by default.
-   *
-   * @deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0. Use
-   *   Drupal\Core\Entity\RevisionableEntityBundleInterface::shouldCreateNewRevision()
-   *   instead.
    */
   public function isNewRevision();
 
   /**
    * Sets whether a new revision should be created by default.
    *
-   * @param bool $new_revision
+   * @param bool $new_revision_
    *   TRUE if a new revision should be created by default.
    */
   public function setNewRevision($new_revision);
diff --git a/core/modules/node/src/Tests/PagePreviewTest.php b/core/modules/node/src/Tests/PagePreviewTest.php
index 2afcc8d..2bfd2e7 100644
--- a/core/modules/node/src/Tests/PagePreviewTest.php
+++ b/core/modules/node/src/Tests/PagePreviewTest.php
@@ -28,7 +28,7 @@ class PagePreviewTest extends NodeTestBase {
    *
    * @var array
    */
-  public static $modules = array('node', 'taxonomy', 'comment', 'image', 'file', 'text', 'node_test', 'menu_ui');
+  public static $modules = array('node', 'taxonomy', 'comment', 'image', 'file');
 
   /**
    * The name of the created field.
@@ -41,7 +41,7 @@ protected function setUp() {
     parent::setUp();
     $this->addDefaultCommentField('node', 'page');
 
-    $web_user = $this->drupalCreateUser(array('edit own page content', 'create page content', 'administer menu'));
+    $web_user = $this->drupalCreateUser(array('edit own page content', 'create page content'));
     $this->drupalLogin($web_user);
 
     // Add a vocabulary so we can test different view modes.
@@ -124,34 +124,6 @@ protected function setUp() {
     entity_get_display('node', 'page', 'default')
       ->setComponent('field_image')
       ->save();
-
-    // Create a multi-value text field.
-    $field_storage = FieldStorageConfig::create([
-      'field_name' => 'field_test_multi',
-      'entity_type' => 'node',
-      'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
-      'type' => 'text',
-      'settings' => [
-        'max_length' => 50,
-      ]
-    ]);
-    $field_storage->save();
-    FieldConfig::create([
-      'field_storage' => $field_storage,
-      'bundle' => 'page',
-    ])->save();
-
-    entity_get_form_display('node', 'page', 'default')
-      ->setComponent('field_test_multi', array(
-        'type' => 'text_textfield',
-      ))
-      ->save();
-
-    entity_get_display('node', 'page', 'default')
-      ->setComponent('field_test_multi', array(
-        'type' => 'string',
-      ))
-      ->save();
   }
 
   /**
@@ -204,11 +176,8 @@ function testPagePreview() {
     $this->clickLink(t('Back to content editing'));
     $this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
     $this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
-    $this->assertFieldByName($term_key, $edit[$term_key], 'Term field displayed.');
+    $this->assertFieldByName($term_key, $edit[$term_key] . ' (' . $this->term->id() . ')', 'Term field displayed.');
     $this->assertFieldByName('field_image[0][alt]', 'Picture of llamas');
-    $this->drupalPostAjaxForm(NULL, array(), array('field_test_multi_add_more' => t('Add another item')), NULL, array(), array(), 'node-page-form');
-    $this->assertFieldByName('field_test_multi[0][value]');
-    $this->assertFieldByName('field_test_multi[1][value]');
 
     // Return to page preview to check everything is as expected.
     $this->drupalPostForm(NULL, array(), t('Preview'));
@@ -222,7 +191,7 @@ function testPagePreview() {
     $this->drupalGet('node/add/page', array('query' => array('uuid' => $uuid)));
     $this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
     $this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
-    $this->assertFieldByName($term_key, $edit[$term_key], 'Term field displayed.');
+    $this->assertFieldByName($term_key, $edit[$term_key] . ' (' . $this->term->id() . ')', 'Term field displayed.');
 
     // Save the node - this is a new POST, so we need to upload the image.
     $this->drupalPostForm('node/add/page', $edit, t('Upload'));
@@ -291,80 +260,6 @@ function testPagePreview() {
     $this->drupalPostForm('node/add/page', array($title_key => 'Preview'), t('Preview'));
     $this->clickLink(t('Back to content editing'));
     $this->assertRaw('edit-submit');
-
-    // Assert multiple items can be added and are not lost when previewing.
-    $test_image_1 = current($this->drupalGetTestFiles('image', 39325));
-    $edit_image_1['files[field_image_0][]'] = drupal_realpath($test_image_1->uri);
-    $test_image_2 = current($this->drupalGetTestFiles('image', 39325));
-    $edit_image_2['files[field_image_1][]'] = drupal_realpath($test_image_2->uri);
-    $edit['field_image[0][alt]'] = 'Alt 1';
-
-    $this->drupalPostForm('node/add/page', $edit_image_1, t('Upload'));
-    $this->drupalPostForm(NULL, $edit, t('Preview'));
-    $this->clickLink(t('Back to content editing'));
-    $this->assertFieldByName('files[field_image_1][]');
-    $this->drupalPostForm(NULL, $edit_image_2, t('Upload'));
-    $this->assertNoFieldByName('files[field_image_1][]');
-
-    $title = 'node_test_title';
-    $example_text_1 = 'example_text_preview_1';
-    $example_text_2 = 'example_text_preview_2';
-    $example_text_3 = 'example_text_preview_3';
-    $this->drupalGet('node/add/page');
-    $edit = [
-      'title[0][value]' => $title,
-      'field_test_multi[0][value]' => $example_text_1,
-    ];
-    $this->assertRaw('Storage is not set');
-    $this->drupalPostForm(NULL, $edit, t('Preview'));
-    $this->clickLink(t('Back to content editing'));
-    $this->assertRaw('Storage is set');
-    $this->assertFieldByName('field_test_multi[0][value]');
-    $this->drupalPostForm(NULL, [], t('Save'));
-    $this->assertText('Basic page ' . $title . ' has been created.');
-    $node = $this->drupalGetNodeByTitle($title);
-    $this->drupalGet('node/' . $node->id() . '/edit');
-    $this->drupalPostAjaxForm(NULL, [], array('field_test_multi_add_more' => t('Add another item')));
-    $this->drupalPostAjaxForm(NULL, [], array('field_test_multi_add_more' => t('Add another item')));
-    $edit = [
-      'field_test_multi[1][value]' => $example_text_2,
-      'field_test_multi[2][value]' => $example_text_3,
-    ];
-    $this->drupalPostForm(NULL, $edit, t('Preview'));
-    $this->clickLink(t('Back to content editing'));
-    $this->drupalPostForm(NULL, $edit, t('Preview'));
-    $this->clickLink(t('Back to content editing'));
-    $this->assertFieldByName('field_test_multi[0][value]', $example_text_1);
-    $this->assertFieldByName('field_test_multi[1][value]', $example_text_2);
-    $this->assertFieldByName('field_test_multi[2][value]', $example_text_3);
-
-    // Now save the node and make sure all values got saved.
-    $this->drupalPostForm(NULL, [], t('Save'));
-    $this->assertText($example_text_1);
-    $this->assertText($example_text_2);
-    $this->assertText($example_text_3);
-
-    // Edit again, change the menu_ui settings and click on preview.
-    $this->drupalGet('node/' . $node->id() . '/edit');
-    $edit = [
-      'menu[enabled]' => TRUE,
-      'menu[title]' => 'Changed title',
-    ];
-    $this->drupalPostForm(NULL, $edit, t('Preview'));
-    $this->clickLink(t('Back to content editing'));
-    $this->assertFieldChecked('edit-menu-enabled', 'Menu option is still checked');
-    $this->assertFieldByName('menu[title]', 'Changed title', 'Menu link title is correct after preview');
-
-    // Save, change the title while saving and make sure that it is correctly
-    // saved.
-    $edit = [
-      'menu[enabled]' => TRUE,
-      'menu[title]' => 'Second title change',
-    ];
-    $this->drupalPostForm(NULL, $edit, t('Save'));
-    $this->drupalGet('node/' . $node->id() . '/edit');
-    $this->assertFieldByName('menu[title]', 'Second title change', 'Menu link title is correct after saving');
-
   }
 
   /**
diff --git a/core/modules/node/tests/modules/node_test/node_test.module b/core/modules/node/tests/modules/node_test/node_test.module
index 866be19..00cff3b 100644
--- a/core/modules/node/tests/modules/node_test/node_test.module
+++ b/core/modules/node/tests/modules/node_test/node_test.module
@@ -10,11 +10,9 @@
 
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
-use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\node\NodeInterface;
 
-
 /**
  * Implements hook_ENTITY_TYPE_view() for node entities.
  */
@@ -177,16 +175,3 @@ function node_test_node_insert(NodeInterface $node) {
     $node->save();
   }
 }
-
-/**
- * Implements hook_form_alter().
- */
-function node_test_form_alter(&$form, FormStateInterface $form_state, $form_id) {
-  if (!$form_state->get('node_test_form_alter')) {
-    drupal_set_message('Storage is not set');
-    $form_state->set('node_test_form_alter', TRUE);
-  }
-  else {
-    drupal_set_message('Storage is set');
-  }
-}
diff --git a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php
index 2f5c20a..0f84d3d 100644
--- a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php
+++ b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php
@@ -29,7 +29,7 @@ class EntitySerializationTest extends NormalizerTestBase {
   /**
    * The test entity.
    *
-   * @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\Core\Entity\RevisionLogInterface
+   * @var \Drupal\Core\Entity\ContentEntityBase
    */
   protected $entity;
 
@@ -76,10 +76,6 @@ protected function setUp() {
         'value' => $this->randomMachineName(),
         'format' => 'full_html',
       ),
-      'revision_log_message' => array(
-        'value' => 'Serialization revision message',
-      ),
-      'revision_user' => $this->user->id(),
     );
     $this->entity = EntityTestMulRev::create($this->values);
     $this->entity->save();
@@ -127,20 +123,6 @@ public function testNormalize() {
         array('value' => TRUE),
       ),
       'non_rev_field' => array(),
-      'revision_created' => array(
-        array('value' => $this->entity->getRevisionCreationTime()),
-      ),
-      'revision_user' => array(
-        array(
-          'target_id' => $this->user->id(),
-          'target_type' => $this->user->getEntityTypeId(),
-          'target_uuid' => $this->user->uuid(),
-          'url' => $this->user->url(),
-        ),
-      ),
-      'revision_log_message' => array(
-        array('value' => $this->values['revision_log_message']['value']),
-      ),
       'field_test_text' => array(
         array(
           'value' => $this->values['field_test_text']['value'],
@@ -210,9 +192,6 @@ public function testSerialize() {
       'revision_id' => '<revision_id><value>' . $this->entity->getRevisionId() . '</value></revision_id>',
       'default_langcode' => '<default_langcode><value>1</value></default_langcode>',
       'non_rev_field' => '<non_rev_field/>',
-      'revision_created' => '<revision_created><value>' . $this->entity->getRevisionCreationTime() . '</value></revision_created>',
-      'revision_user' => '<revision_user><target_id>' . $this->user->id() . '</target_id><target_type>' . $this->user->getEntityTypeId() . '</target_type><target_uuid>' . $this->user->uuid() . '</target_uuid><url>' . $this->user->url() . '</url></revision_user>',
-      'revision_log_message' => '<revision_log_message><value>' . $this->values['revision_log_message']['value'] . '</value></revision_log_message>',
       'field_test_text' => '<field_test_text><value>' . $this->values['field_test_text']['value'] . '</value><format>' . $this->values['field_test_text']['format'] . '</format></field_test_text>',
     );
     // Sort it in the same order as normalised.
diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php
index 14d7909..2574008 100644
--- a/core/modules/simpletest/src/TestBase.php
+++ b/core/modules/simpletest/src/TestBase.php
@@ -10,7 +10,6 @@
 use Drupal\Core\Site\Settings;
 use Drupal\Core\StreamWrapper\PublicStream;
 use Drupal\Core\Test\TestDatabase;
-use Drupal\Core\Test\TestSetupTrait;
 use Drupal\Core\Utility\Error;
 use Drupal\Tests\ConfigTestTrait;
 use Drupal\Tests\RandomGeneratorTrait;
@@ -23,7 +22,6 @@
  */
 abstract class TestBase {
 
-  use TestSetupTrait;
   use SessionTestTrait;
   use RandomGeneratorTrait;
   use AssertHelperTrait;
@@ -34,6 +32,20 @@
   }
 
   /**
+   * The test run ID.
+   *
+   * @var string
+   */
+  protected $testId;
+
+  /**
+   * The site directory of this test run.
+   *
+   * @var string
+   */
+  protected $siteDirectory = NULL;
+
+  /**
    * The database prefix of this test run.
    *
    * @var string
@@ -166,6 +178,13 @@
   protected $originalPrefix;
 
   /**
+   * The original installation profile.
+   *
+   * @var string
+   */
+  protected $originalProfile;
+
+  /**
    * The name of the session cookie of the test-runner.
    *
    * @var string
@@ -187,6 +206,13 @@
   protected $originalShutdownCallbacks;
 
   /**
+   * The site directory of the original parent site.
+   *
+   * @var string
+   */
+  protected $originalSite;
+
+  /**
    * The original user, before testing began.
    *
    * @var \Drupal\Core\Session\AccountProxyInterface
@@ -194,6 +220,33 @@
   protected $originalUser;
 
   /**
+   * The public file directory for the test environment.
+   *
+   * This is set in TestBase::prepareEnvironment().
+   *
+   * @var string
+   */
+  protected $publicFilesDirectory;
+
+  /**
+   * The private file directory for the test environment.
+   *
+   * This is set in TestBase::prepareEnvironment().
+   *
+   * @var string
+   */
+  protected $privateFilesDirectory;
+
+  /**
+   * The temporary file directory for the test environment.
+   *
+   * This is set in TestBase::prepareEnvironment().
+   *
+   * @var string
+   */
+  protected $tempFilesDirectory;
+
+  /**
    * The translation file directory for the test environment.
    *
    * This is set in TestBase::prepareEnvironment().
@@ -212,6 +265,20 @@
   public $dieOnFail = FALSE;
 
   /**
+   * The DrupalKernel instance used in the test.
+   *
+   * @var \Drupal\Core\DrupalKernel
+   */
+  protected $kernel;
+
+  /**
+   * The dependency injection container used in the test.
+   *
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
+   */
+  protected $container;
+
+  /**
    * The config importer that can used in a test.
    *
    * @var \Drupal\Core\Config\ConfigImporter
@@ -219,6 +286,31 @@
   protected $configImporter;
 
   /**
+   * Set to TRUE to strict check all configuration saved.
+   *
+   * @see \Drupal\Core\Config\Development\ConfigSchemaChecker
+   *
+   * @var bool
+   */
+  protected $strictConfigSchema = TRUE;
+
+  /**
+   * An array of config object names that are excluded from schema checking.
+   *
+   * @var string[]
+   */
+  protected static $configSchemaCheckerExclusions = array(
+    // Following are used to test lack of or partial schema. Where partial
+    // schema is provided, that is explicitly tested in specific tests.
+    'config_schema_test.noschema',
+    'config_schema_test.someschema',
+    'config_schema_test.schema_data_types',
+    'config_schema_test.no_schema_data_types',
+    // Used to test application of schema to filtering of configuration.
+    'config_test.dynamic.system',
+  );
+
+  /**
    * HTTP authentication method (specified as a CURLAUTH_* constant).
    *
    * @var int
@@ -414,6 +506,16 @@ public static function deleteAssert($message_id) {
   }
 
   /**
+   * Returns the database connection to the site running Simpletest.
+   *
+   * @return \Drupal\Core\Database\Connection
+   *   The database connection to use for inserting assertions.
+   */
+  public static function getDatabaseConnection() {
+    return TestDatabase::getConnection();
+  }
+
+  /**
    * Cycles through backtrace until the first non-assertion method is found.
    *
    * @return
@@ -1023,6 +1125,36 @@ private function prepareDatabasePrefix() {
   }
 
   /**
+   * Changes the database connection to the prefixed one.
+   *
+   * @see TestBase::prepareEnvironment()
+   */
+  private function changeDatabasePrefix() {
+    if (empty($this->databasePrefix)) {
+      $this->prepareDatabasePrefix();
+    }
+    // If the backup already exists, something went terribly wrong.
+    // This case is possible, because database connection info is a static
+    // global state construct on the Database class, which at least persists
+    // for all test methods executed in one PHP process.
+    if (Database::getConnectionInfo('simpletest_original_default')) {
+      throw new \RuntimeException("Bad Database connection state: 'simpletest_original_default' connection key already exists. Broken test?");
+    }
+
+    // Clone the current connection and replace the current prefix.
+    $connection_info = Database::getConnectionInfo('default');
+    Database::renameConnection('default', 'simpletest_original_default');
+    foreach ($connection_info as $target => $value) {
+      // Replace the full table prefix definition to ensure that no table
+      // prefixes of the test runner leak into the test.
+      $connection_info[$target]['prefix'] = array(
+        'default' => $value['prefix']['default'] . $this->databasePrefix,
+      );
+    }
+    Database::addConnectionInfo('default', 'default', $connection_info['default']);
+  }
+
+  /**
    * Act on global state information before the environment is altered for a test.
    *
    * Allows e.g. KernelTestBase to prime system/extension info from the
@@ -1440,4 +1572,23 @@ public function getTempFilesDirectory() {
     return $this->tempFilesDirectory;
   }
 
+  /**
+   * Gets the config schema exclusions for this test.
+   *
+   * @return string[]
+   *   An array of config object names that are excluded from schema checking.
+   */
+  protected function getConfigSchemaExclusions() {
+    $class = get_class($this);
+    $exceptions = [];
+    while ($class) {
+      if (property_exists($class, 'configSchemaCheckerExclusions')) {
+        $exceptions = array_merge($exceptions, $class::$configSchemaCheckerExclusions);
+      }
+      $class = get_parent_class($class);
+    }
+    // Filter out any duplicates.
+    return array_unique($exceptions);
+  }
+
 }
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index a793865..9d2bf6a 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -3,24 +3,34 @@
 namespace Drupal\simpletest;
 
 use Drupal\block\Entity\Block;
+use Drupal\Component\FileCache\FileCacheFactory;
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Config\Development\ConfigSchemaChecker;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Database\Database;
+use Drupal\Core\DrupalKernel;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
+use Drupal\Core\Extension\MissingDependencyException;
 use Drupal\Core\Render\Element;
+use Drupal\Core\Serialization\Yaml;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Session\AnonymousUserSession;
+use Drupal\Core\Session\UserSession;
+use Drupal\Core\Site\Settings;
 use Drupal\Core\Test\AssertMailTrait;
-use Drupal\Core\Test\FunctionalTestSetupTrait;
 use Drupal\Core\Url;
 use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
 use Drupal\Tests\TestFileCreationTrait;
 use Drupal\Tests\XdebugRequestTrait;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Yaml\Yaml as SymfonyYaml;
 use Zend\Diactoros\Uri;
 
 /**
@@ -30,7 +40,6 @@
  */
 abstract class WebTestBase extends TestBase {
 
-  use FunctionalTestSetupTrait;
   use AssertContentTrait;
   use TestFileCreationTrait {
     getTestFiles as drupalGetTestFiles;
@@ -118,6 +127,14 @@
   protected $loggedInUser = FALSE;
 
   /**
+   * The "#1" admin user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $rootUser;
+
+
+  /**
    * The current cookie file used by cURL.
    *
    * We do not reuse the cookies in further runs, so we do not need a file
@@ -185,6 +202,18 @@
   protected $metaRefreshCount = 0;
 
   /**
+   * The kernel used in this test.
+   *
+   * @var \Drupal\Core\DrupalKernel
+   */
+  protected $kernel;
+
+  /**
+   * The config directories used in this test.
+   */
+  protected $configDirectories = array();
+
+  /**
    * Cookies to set on curl requests.
    *
    * @var array
@@ -199,6 +228,13 @@
   protected $customTranslations;
 
   /**
+   * The class loader to use for installation and initialization of setup.
+   *
+   * @var \Symfony\Component\Classloader\Classloader
+   */
+  protected $classLoader;
+
+  /**
    * Constructor for \Drupal\simpletest\WebTestBase.
    */
   function __construct($test_id = NULL) {
@@ -444,6 +480,178 @@ protected function setUp() {
   }
 
   /**
+   * Execute the non-interactive installer.
+   *
+   * @see install_drupal()
+   */
+  protected function doInstall() {
+    require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
+    install_drupal($this->classLoader, $this->installParameters());
+  }
+
+  /**
+   * Prepares site settings and services before installation.
+   */
+  protected function prepareSettings() {
+    // Prepare installer settings that are not install_drupal() parameters.
+    // Copy and prepare an actual settings.php, so as to resemble a regular
+    // installation.
+    // Not using File API; a potential error must trigger a PHP warning.
+    $directory = DRUPAL_ROOT . '/' . $this->siteDirectory;
+    copy(DRUPAL_ROOT . '/sites/default/default.settings.php', $directory . '/settings.php');
+
+    // All file system paths are created by System module during installation.
+    // @see system_requirements()
+    // @see TestBase::prepareEnvironment()
+    $settings['settings']['file_public_path'] = (object) [
+      'value' => $this->publicFilesDirectory,
+      'required' => TRUE,
+    ];
+    $settings['settings']['file_private_path'] = (object) [
+      'value' => $this->privateFilesDirectory,
+      'required' => TRUE,
+    ];
+    // Save the original site directory path, so that extensions in the
+    // site-specific directory can still be discovered in the test site
+    // environment.
+    // @see \Drupal\Core\Extension\ExtensionDiscovery::scan()
+    $settings['settings']['test_parent_site'] = (object) [
+      'value' => $this->originalSite,
+      'required' => TRUE,
+    ];
+    // Add the parent profile's search path to the child site's search paths.
+    // @see \Drupal\Core\Extension\ExtensionDiscovery::getProfileDirectories()
+    $settings['conf']['simpletest.settings']['parent_profile'] = (object) [
+      'value' => $this->originalProfile,
+      'required' => TRUE,
+    ];
+    $this->writeSettings($settings);
+    // Allow for test-specific overrides.
+    $settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSite . '/settings.testing.php';
+    if (file_exists($settings_testing_file)) {
+      // Copy the testing-specific settings.php overrides in place.
+      copy($settings_testing_file, $directory . '/settings.testing.php');
+      // Add the name of the testing class to settings.php and include the
+      // testing specific overrides
+      file_put_contents($directory . '/settings.php', "\n\$test_class = '" . get_class($this) . "';\n" . 'include DRUPAL_ROOT . \'/\' . $site_path . \'/settings.testing.php\';' . "\n", FILE_APPEND);
+    }
+    $settings_services_file = DRUPAL_ROOT . '/' . $this->originalSite . '/testing.services.yml';
+    if (!file_exists($settings_services_file)) {
+      // Otherwise, use the default services as a starting point for overrides.
+      $settings_services_file = DRUPAL_ROOT . '/sites/default/default.services.yml';
+    }
+    // Copy the testing-specific service overrides in place.
+    copy($settings_services_file, $directory . '/services.yml');
+    if ($this->strictConfigSchema) {
+      // Add a listener to validate configuration schema on save.
+      $yaml = new SymfonyYaml();
+      $content = file_get_contents($directory . '/services.yml');
+      $services = $yaml->parse($content);
+      $services['services']['simpletest.config_schema_checker'] = [
+        'class' => ConfigSchemaChecker::class,
+        'arguments' => ['@config.typed', $this->getConfigSchemaExclusions()],
+        'tags' => [['name' => 'event_subscriber']]
+      ];
+      file_put_contents($directory . '/services.yml', $yaml->dump($services));
+    }
+    // Since Drupal is bootstrapped already, install_begin_request() will not
+    // bootstrap again. Hence, we have to reload the newly written custom
+    // settings.php manually.
+    Settings::initialize(DRUPAL_ROOT, $this->siteDirectory, $this->classLoader);
+  }
+
+  /**
+   * Initialize settings created during install.
+   */
+  protected function initSettings() {
+    Settings::initialize(DRUPAL_ROOT, $this->siteDirectory, $this->classLoader);
+    foreach ($GLOBALS['config_directories'] as $type => $path) {
+      $this->configDirectories[$type] = $path;
+    }
+
+    // After writing settings.php, the installer removes write permissions
+    // from the site directory. To allow drupal_generate_test_ua() to write
+    // a file containing the private key for drupal_valid_test_ua(), the site
+    // directory has to be writable.
+    // TestBase::restoreEnvironment() will delete the entire site directory.
+    // Not using File API; a potential error must trigger a PHP warning.
+    chmod(DRUPAL_ROOT . '/' . $this->siteDirectory, 0777);
+
+    // During tests, cacheable responses should get the debugging cacheability
+    // headers by default.
+    $this->setContainerParameter('http.response.debug_cacheability_headers', TRUE);
+  }
+
+  /**
+   * Initialize various configurations post-installation.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+   *   The container.
+   */
+  protected function initConfig(ContainerInterface $container) {
+    $config = $container->get('config.factory');
+
+    // Manually create and configure private and temporary files directories.
+    // While these could be preset/enforced in settings.php like the public
+    // files directory above, some tests expect them to be configurable in the
+    // UI. If declared in settings.php, they would no longer be configurable.
+    file_prepare_directory($this->privateFilesDirectory, FILE_CREATE_DIRECTORY);
+    file_prepare_directory($this->tempFilesDirectory, FILE_CREATE_DIRECTORY);
+    $config->getEditable('system.file')
+      ->set('path.temporary', $this->tempFilesDirectory)
+      ->save();
+
+    // Manually configure the test mail collector implementation to prevent
+    // tests from sending out emails and collect them in state instead.
+    // While this should be enforced via settings.php prior to installation,
+    // some tests expect to be able to test mail system implementations.
+    $config->getEditable('system.mail')
+      ->set('interface.default', 'test_mail_collector')
+      ->save();
+
+    // By default, verbosely display all errors and disable all production
+    // environment optimizations for all tests to avoid needless overhead and
+    // ensure a sane default experience for test authors.
+    // @see https://www.drupal.org/node/2259167
+    $config->getEditable('system.logging')
+      ->set('error_level', 'verbose')
+      ->save();
+    $config->getEditable('system.performance')
+      ->set('css.preprocess', FALSE)
+      ->set('js.preprocess', FALSE)
+      ->save();
+
+    // Set an explicit time zone to not rely on the system one, which may vary
+    // from setup to setup. The Australia/Sydney time zone is chosen so all
+    // tests are run using an edge case scenario (UTC+10 and DST). This choice
+    // is made to prevent time zone related regressions and reduce the
+    // fragility of the testing system in general.
+    $config->getEditable('system.date')
+      ->set('timezone.default', 'Australia/Sydney')
+      ->save();
+  }
+
+  /**
+   * Reset and rebuild the environment after setup.
+   */
+  protected function rebuildAll() {
+    // Reset/rebuild all data structures after enabling the modules, primarily
+    // to synchronize all data structures and caches between the test runner and
+    // the child site.
+    // @see \Drupal\Core\DrupalKernel::bootCode()
+    // @todo Test-specific setUp() methods may set up further fixtures; find a
+    //   way to execute this after setUp() is done, or to eliminate it entirely.
+    $this->resetAll();
+    $this->kernel->prepareLegacyRequest(\Drupal::request());
+
+    // Explicitly call register() again on the container registered in \Drupal.
+    // @todo This should already be called through
+    //   DrupalKernel::prepareLegacyRequest() -> DrupalKernel::boot() but that
+    //   appears to be calling a different container.
+    $this->container->get('stream_wrapper_manager')->register();
+  }
+
+  /**
    * Returns the parameters that will be used when Simpletest installs Drupal.
    *
    * @see install_drupal()
@@ -536,6 +744,77 @@ protected function restoreBatch() {
   }
 
   /**
+   * Initializes user 1 for the site to be installed.
+   */
+  protected function initUserSession() {
+    // Define information about the user 1 account.
+    $this->rootUser = new UserSession(array(
+      'uid' => 1,
+      'name' => 'admin',
+      'mail' => 'admin@example.com',
+      'pass_raw' => $this->randomMachineName(),
+      'timezone' => date_default_timezone_get(),
+    ));
+
+    // The child site derives its session name from the database prefix when
+    // running web tests.
+    $this->generateSessionName($this->databasePrefix);
+  }
+
+  /**
+   * Initializes the kernel after installation.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   Request object.
+   *
+   * @return \Symfony\Component\DependencyInjection\ContainerInterface
+   *   The container.
+   */
+  protected function initKernel(Request $request) {
+    $this->kernel = DrupalKernel::createFromRequest($request, $this->classLoader, 'prod', TRUE);
+    $this->kernel->prepareLegacyRequest($request);
+    // Force the container to be built from scratch instead of loaded from the
+    // disk. This forces us to not accidentally load the parent site.
+    return $this->kernel->rebuildContainer();
+  }
+
+  /**
+   * Install modules defined by `static::$modules`.
+   *
+   * To install test modules outside of the testing environment, add
+   * @code
+   * $settings['extension_discovery_scan_tests'] = TRUE;
+   * @endcode
+   * to your settings.php.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+   *   The container.
+   */
+  protected function installModulesFromClassProperty(ContainerInterface $container) {
+    $class = get_class($this);
+    $modules = [];
+    while ($class) {
+      if (property_exists($class, 'modules')) {
+        $modules = array_merge($modules, $class::$modules);
+      }
+      $class = get_parent_class($class);
+    }
+    if ($modules) {
+      $modules = array_unique($modules);
+      try {
+        $success = $container->get('module_installer')->install($modules, TRUE);
+        $this->assertTrue($success, SafeMarkup::format('Enabled modules: %modules', ['%modules' => implode(', ', $modules)]));
+      }
+      catch (MissingDependencyException $e) {
+        // The exception message has all the details.
+        $this->fail($e->getMessage());
+      }
+
+      $this->rebuildContainer();
+    }
+  }
+
+  /**
    * Returns all supported database driver installer objects.
    *
    * This wraps drupal_get_database_types() for use without a current container.
@@ -551,6 +830,46 @@ protected function getDatabaseTypes() {
   }
 
   /**
+   * Rewrites the settings.php file of the test site.
+   *
+   * @param array $settings
+   *   An array of settings to write out, in the format expected by
+   *   drupal_rewrite_settings().
+   *
+   * @see drupal_rewrite_settings()
+   */
+  protected function writeSettings(array $settings) {
+    include_once DRUPAL_ROOT . '/core/includes/install.inc';
+    $filename = $this->siteDirectory . '/settings.php';
+    // system_requirements() removes write permissions from settings.php
+    // whenever it is invoked.
+    // Not using File API; a potential error must trigger a PHP warning.
+    chmod($filename, 0666);
+    drupal_rewrite_settings($settings, $filename);
+  }
+
+  /**
+   * Changes parameters in the services.yml file.
+   *
+   * @param $name
+   *   The name of the parameter.
+   * @param $value
+   *   The value of the parameter.
+   */
+  protected function setContainerParameter($name, $value) {
+    $filename = $this->siteDirectory . '/services.yml';
+    chmod($filename, 0666);
+
+    $services = Yaml::decode(file_get_contents($filename));
+    $services['parameters'][$name] = $value;
+    file_put_contents($filename, Yaml::encode($services));
+
+    // Ensure that the cache is deleted for the yaml file loader.
+    $file_cache = FileCacheFactory::get('container_yaml_loader');
+    $file_cache->delete($filename);
+  }
+
+  /**
    * Queues custom translations to be written to settings.php.
    *
    * Use WebTestBase::writeCustomTranslations() to apply and write the queued
@@ -613,6 +932,73 @@ protected function writeCustomTranslations() {
   }
 
   /**
+   * Rebuilds \Drupal::getContainer().
+   *
+   * Use this to update the test process's kernel with a new service container.
+   * For example, when the list of enabled modules is changed via the internal
+   * browser the test process's kernel has a service container with an out of
+   * date module list.
+   *
+   * @see TestBase::prepareEnvironment()
+   * @see TestBase::restoreEnvironment()
+   *
+   * @todo Fix https://www.drupal.org/node/2021959 so that module enable/disable
+   *   changes are immediately reflected in \Drupal::getContainer(). Until then,
+   *   tests can invoke this workaround when requiring services from newly
+   *   enabled modules to be immediately available in the same request.
+   */
+  protected function rebuildContainer() {
+    // Rebuild the kernel and bring it back to a fully bootstrapped state.
+    $this->container = $this->kernel->rebuildContainer();
+
+    // Make sure the url generator has a request object, otherwise calls to
+    // $this->drupalGet() will fail.
+    $this->prepareRequestForGenerator();
+  }
+
+  /**
+   * Resets all data structures after having enabled new modules.
+   *
+   * This method is called by \Drupal\simpletest\WebTestBase::setUp() after
+   * enabling the requested modules. It must be called again when additional
+   * modules are enabled later.
+   */
+  protected function resetAll() {
+    // Clear all database and static caches and rebuild data structures.
+    drupal_flush_all_caches();
+    $this->container = \Drupal::getContainer();
+
+    // Reset static variables and reload permissions.
+    $this->refreshVariables();
+  }
+
+  /**
+   * Refreshes in-memory configuration and state information.
+   *
+   * Useful after a page request is made that changes configuration or state in
+   * a different thread.
+   *
+   * In other words calling a settings page with $this->drupalPostForm() with a
+   * changed value would update configuration to reflect that change, but in the
+   * thread that made the call (thread running the test) the changed values
+   * would not be picked up.
+   *
+   * This method clears the cache and loads a fresh copy.
+   */
+  protected function refreshVariables() {
+    // Clear the tag cache.
+    \Drupal::service('cache_tags.invalidator')->resetChecksums();
+    foreach (Cache::getBins() as $backend) {
+      if (is_callable(array($backend, 'reset'))) {
+        $backend->reset();
+      }
+    }
+
+    $this->container->get('config.factory')->reset();
+    $this->container->get('state')->resetCache();
+  }
+
+  /**
    * Cleans up after testing.
    *
    * Deletes created files and temporary files directory, deletes the tables
@@ -2119,6 +2505,60 @@ protected function assertNoResponse($code, $message = '', $group = 'Browser') {
   }
 
   /**
+   * Creates a mock request and sets it on the generator.
+   *
+   * This is used to manipulate how the generator generates paths during tests.
+   * It also ensures that calls to $this->drupalGet() will work when running
+   * from run-tests.sh because the url generator no longer looks at the global
+   * variables that are set there but relies on getting this information from a
+   * request object.
+   *
+   * @param bool $clean_urls
+   *   Whether to mock the request using clean urls.
+   * @param $override_server_vars
+   *   An array of server variables to override.
+   *
+   * @return \Symfony\Component\HttpFoundation\Request
+   *   The mocked request object.
+   */
+  protected function prepareRequestForGenerator($clean_urls = TRUE, $override_server_vars = array()) {
+    $request = Request::createFromGlobals();
+    $server = $request->server->all();
+    if (basename($server['SCRIPT_FILENAME']) != basename($server['SCRIPT_NAME'])) {
+      // We need this for when the test is executed by run-tests.sh.
+      // @todo Remove this once run-tests.sh has been converted to use a Request
+      //   object.
+      $cwd = getcwd();
+      $server['SCRIPT_FILENAME'] = $cwd . '/' . basename($server['SCRIPT_NAME']);
+      $base_path = rtrim($server['REQUEST_URI'], '/');
+    }
+    else {
+      $base_path = $request->getBasePath();
+    }
+    if ($clean_urls) {
+      $request_path = $base_path ? $base_path . '/user' : 'user';
+    }
+    else {
+      $request_path = $base_path ? $base_path . '/index.php/user' : '/index.php/user';
+    }
+    $server = array_merge($server, $override_server_vars);
+
+    $request = Request::create($request_path, 'GET', array(), array(), array(), $server);
+    // Ensure the request time is REQUEST_TIME to ensure that API calls
+    // in the test use the right timestamp.
+    $request->server->set('REQUEST_TIME', REQUEST_TIME);
+    $this->container->get('request_stack')->push($request);
+
+    // The request context is normally set by the router_listener from within
+    // its KernelEvents::REQUEST listener. In the simpletest parent site this
+    // event is not fired, therefore it is necessary to updated the request
+    // context manually here.
+    $this->container->get('router.request_context')->fromRequest($request);
+
+    return $request;
+  }
+
+  /**
    * Builds an a absolute URL from a system path or a URL object.
    *
    * @param string|\Drupal\Core\Url $path
diff --git a/core/modules/statistics/config/install/statistics.settings.yml b/core/modules/statistics/config/install/statistics.settings.yml
index 6686062..f788bec 100644
--- a/core/modules/statistics/config/install/statistics.settings.yml
+++ b/core/modules/statistics/config/install/statistics.settings.yml
@@ -1,2 +1,5 @@
+access_log:
+  enabled: false
+  max_lifetime: 259200
 count_content_views: 0
 display_max_age: 3600
diff --git a/core/modules/statistics/config/schema/statistics.schema.yml b/core/modules/statistics/config/schema/statistics.schema.yml
index c72a227..d5e6200 100644
--- a/core/modules/statistics/config/schema/statistics.schema.yml
+++ b/core/modules/statistics/config/schema/statistics.schema.yml
@@ -4,6 +4,16 @@ statistics.settings:
   type: config_object
   label: 'Statistics settings'
   mapping:
+    access_log:
+      type: mapping
+      label: 'Access log settings'
+      mapping:
+        enabled:
+          type: boolean
+          label: 'Enable'
+        max_lifetime:
+          type: integer
+          label: 'Maximum lifetime'
     count_content_views:
       type: integer
       label: 'Count content views'
diff --git a/core/modules/statistics/migration_templates/statistics_settings.yml b/core/modules/statistics/migration_templates/statistics_settings.yml
index 1f5b5bb..62c2c06 100644
--- a/core/modules/statistics/migration_templates/statistics_settings.yml
+++ b/core/modules/statistics/migration_templates/statistics_settings.yml
@@ -10,6 +10,8 @@ source:
     - statistics_flush_accesslog_timer
     - statistics_count_content_views
 process:
+  'access_log/enabled': statistics_enable_access_log
+  'access_log/max_lifetime': statistics_flush_accesslog_timer
   'count_content_views': statistics_count_content_views
 destination:
   plugin: config
diff --git a/core/modules/statistics/src/Tests/Views/IntegrationTest.php b/core/modules/statistics/src/Tests/Views/IntegrationTest.php
index 601aa6c..7fe99f9 100644
--- a/core/modules/statistics/src/Tests/Views/IntegrationTest.php
+++ b/core/modules/statistics/src/Tests/Views/IntegrationTest.php
@@ -56,8 +56,9 @@ protected function setUp() {
     $this->drupalCreateContentType(array('type' => 'page'));
     $this->node = $this->drupalCreateNode(array('type' => 'page'));
 
-    // Enable counting of content views.
+    // Enable access logging.
     $this->config('statistics.settings')
+      ->set('access_log.enabled', 1)
       ->set('count_content_views', 1)
       ->save();
 
diff --git a/core/modules/statistics/statistics.install b/core/modules/statistics/statistics.install
index f232e1d..11c72f4 100644
--- a/core/modules/statistics/statistics.install
+++ b/core/modules/statistics/statistics.install
@@ -79,10 +79,3 @@ function statistics_update_8002() {
   // Set the new configuration setting for max age to the initial value.
   \Drupal::configFactory()->getEditable('statistics.settings')->set('display_max_age', 3600)->save();
 }
-
-/**
- * Remove access_log settings.
- */
-function statistics_update_8300() {
-  \Drupal::configFactory()->getEditable('statistics.settings')->clear('access_log')->save();
-}
diff --git a/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php b/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php
index 6bd5394..5313eb3 100644
--- a/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php
+++ b/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php
@@ -32,6 +32,8 @@ protected function setUp() {
    */
   public function testStatisticsSettings() {
     $config = $this->config('statistics.settings');
+    $this->assertIdentical(FALSE, $config->get('access_log.enabled'));
+    $this->assertIdentical(259200, $config->get('access_log.max_lifetime'));
     $this->assertIdentical(0, $config->get('count_content_views'));
     $this->assertConfigSchema(\Drupal::service('config.typed'), 'statistics.settings', $config->get());
   }
diff --git a/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateStatisticsConfigsTest.php b/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateStatisticsConfigsTest.php
index e5f915b..c35f3dc 100644
--- a/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateStatisticsConfigsTest.php
+++ b/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateStatisticsConfigsTest.php
@@ -32,6 +32,8 @@ protected function setUp() {
    */
   public function testStatisticsSettings() {
     $config = $this->config('statistics.settings');
+    $this->assertIdentical(TRUE, $config->get('access_log.enabled'));
+    $this->assertIdentical(3600, $config->get('access_log.max_lifetime'));
     $this->assertIdentical(1, $config->get('count_content_views'));
     $this->assertConfigSchema(\Drupal::service('config.typed'), 'statistics.settings', $config->get());
   }
diff --git a/core/modules/syslog/syslog.info.yml b/core/modules/syslog/syslog.info.yml
index ab4f1aa..1d8dc8f 100644
--- a/core/modules/syslog/syslog.info.yml
+++ b/core/modules/syslog/syslog.info.yml
@@ -1,6 +1,6 @@
 name: Syslog
 type: module
-description: 'Logs events by sending messages to the logging facility of the web server.'
+description: 'Logs and records system events to syslog.'
 package: Core
 version: VERSION
 core: 8.x
diff --git a/core/modules/system/src/Plugin/Block/SystemMenuBlock.php b/core/modules/system/src/Plugin/Block/SystemMenuBlock.php
index 24db858..de9ee79 100644
--- a/core/modules/system/src/Plugin/Block/SystemMenuBlock.php
+++ b/core/modules/system/src/Plugin/Block/SystemMenuBlock.php
@@ -89,10 +89,10 @@ public function blockForm($form, FormStateInterface $form_state) {
 
     $form['menu_levels']['level'] = array(
       '#type' => 'select',
-      '#title' => $this->t('Initial visibility level'),
+      '#title' => $this->t('Initial menu level'),
       '#default_value' => $config['level'],
       '#options' => $options,
-      '#description' => $this->t('The menu is only visible if the menu item for the current page is at this level or below it. Use level 1 to always display this menu.'),
+      '#description' => $this->t('The menu will only be visible if the menu item for the current page is at or below the selected starting level. Select level 1 to always keep this menu visible.'),
       '#required' => TRUE,
     );
 
@@ -100,10 +100,10 @@ public function blockForm($form, FormStateInterface $form_state) {
 
     $form['menu_levels']['depth'] = array(
       '#type' => 'select',
-      '#title' => $this->t('Number of levels to display'),
+      '#title' => $this->t('Maximum number of menu levels to display'),
       '#default_value' => $config['depth'],
       '#options' => $options,
-      '#description' => $this->t('This maximum number includes the initial level.'),
+      '#description' => $this->t('The maximum number of menu levels to show, starting from the initial menu level. For example: with an initial level 2 and a maximum number of 3, menu levels 2, 3 and 4 can be displayed.'),
       '#required' => TRUE,
     );
 
diff --git a/core/modules/system/system.post_update.php b/core/modules/system/system.post_update.php
index 01f304b..193a14e 100644
--- a/core/modules/system/system.post_update.php
+++ b/core/modules/system/system.post_update.php
@@ -60,8 +60,8 @@ function system_post_update_add_region_to_entity_displays() {
 
 
 /**
- * Force caches using hashes to be cleared (Twig, render cache, etc.).
+ * Force Twig PHP file cache to be cleared.
  */
-function system_post_update_hashes_clear_cache() {
+function system_post_update_clear_twig_cache() {
   // Empty post-update hook.
 }
diff --git a/core/modules/system/tests/modules/entity_test/config/schema/entity_test.schema.yml b/core/modules/system/tests/modules/entity_test/config/schema/entity_test.schema.yml
index d34d949..464a007 100644
--- a/core/modules/system/tests/modules/entity_test/config/schema/entity_test.schema.yml
+++ b/core/modules/system/tests/modules/entity_test/config/schema/entity_test.schema.yml
@@ -27,3 +27,19 @@ entity_test.entity_test_bundle.*:
     description:
       type: text
       label: 'Description'
+
+entity_test.entity_test_bundle.*.third_party.content_moderation:
+  type: mapping
+  label: 'Enable moderation states for this entity test type'
+  mapping:
+    enabled:
+      type: boolean
+      label: 'Moderation states enabled'
+    allowed_moderation_states:
+      type: sequence
+      sequence:
+        type: string
+        label: 'Moderation state'
+    default_moderation_state:
+      type: string
+      label: 'Moderation state for new entity test'
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
index f01675f..0ee28bf 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
@@ -27,7 +27,6 @@
  *   revision_data_table = "entity_test_mulrev_property_revision",
  *   admin_permission = "administer entity_test content",
  *   translatable = TRUE,
- *   show_revision_ui = TRUE,
  *   entity_keys = {
  *     "id" = "id",
  *     "uuid" = "uuid",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
index 49c5aca..47035ea 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
@@ -3,8 +3,6 @@
 namespace Drupal\entity_test\Entity;
 
 use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Entity\RevisionLogEntityTrait;
-use Drupal\Core\Entity\RevisionLogInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
 
 /**
@@ -30,7 +28,6 @@
  *   base_table = "entity_test_rev",
  *   revision_table = "entity_test_rev_revision",
  *   admin_permission = "administer entity_test content",
- *   show_revision_ui = TRUE,
  *   entity_keys = {
  *     "id" = "id",
  *     "uuid" = "uuid",
@@ -48,9 +45,7 @@
  *   }
  * )
  */
-class EntityTestRev extends EntityTest implements RevisionLogInterface {
-
-  use RevisionLogEntityTrait;
+class EntityTestRev extends EntityTest {
 
   /**
    * {@inheritdoc}
@@ -58,8 +53,13 @@ class EntityTestRev extends EntityTest implements RevisionLogInterface {
   public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     $fields = parent::baseFieldDefinitions($entity_type);
 
-    $fields += static::revisionLogBaseFieldDefinitions($entity_type);
+    $fields['revision_id'] = BaseFieldDefinition::create('integer')
+      ->setLabel(t('Revision ID'))
+      ->setDescription(t('The version id of the test entity.'))
+      ->setReadOnly(TRUE)
+      ->setSetting('unsigned', TRUE);
 
+    $fields['langcode']->setRevisionable(TRUE);
     $fields['name']->setRevisionable(TRUE);
     $fields['user_id']->setRevisionable(TRUE);
 
diff --git a/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php b/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php
index 7b0850e..f02f0b6 100644
--- a/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php
+++ b/core/modules/system/tests/modules/render_placeholder_message_test/src/RenderPlaceholderMessageTestController.php
@@ -14,9 +14,9 @@ class RenderPlaceholderMessageTestController implements ContainerAwareInterface
    */
   public function messagesPlaceholderFirst() {
     return $this->build([
-      '<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></drupal-render-placeholder>',
-      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P1" token="JBp04zOwNhYqMBgRkyBnPdma8m4l2elDnXMJ9tEsP6k"></drupal-render-placeholder>',
-      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P2" token="JnoubSJT1l92Dm4fJw4EPsSzRsmE88H6Q1zu9-OzDh4"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="a8c34b5e"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P1" token="0546ada3"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P2" token="83d2df0d"></drupal-render-placeholder>',
     ]);
   }
 
@@ -25,9 +25,9 @@ public function messagesPlaceholderFirst() {
    */
   public function messagesPlaceholderMiddle() {
     return $this->build([
-      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P1" token="JBp04zOwNhYqMBgRkyBnPdma8m4l2elDnXMJ9tEsP6k"></drupal-render-placeholder>',
-      '<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></drupal-render-placeholder>',
-      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P2" token="JnoubSJT1l92Dm4fJw4EPsSzRsmE88H6Q1zu9-OzDh4"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P1" token="0546ada3"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="a8c34b5e"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P2" token="83d2df0d"></drupal-render-placeholder>',
     ]);
   }
 
@@ -36,9 +36,9 @@ public function messagesPlaceholderMiddle() {
    */
   public function messagesPlaceholderLast() {
     return $this->build([
-      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P1" token="JBp04zOwNhYqMBgRkyBnPdma8m4l2elDnXMJ9tEsP6k"></drupal-render-placeholder>',
-      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P2" token="JnoubSJT1l92Dm4fJw4EPsSzRsmE88H6Q1zu9-OzDh4"></drupal-render-placeholder>',
-      '<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P1" token="0546ada3"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="\Drupal\render_placeholder_message_test\RenderPlaceholderMessageTestController::setAndLogMessage" arguments="0=P2" token="83d2df0d"></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="a8c34b5e"></drupal-render-placeholder>',
     ]);
   }
 
diff --git a/core/modules/taxonomy/migration_templates/d6_taxonomy_term.yml b/core/modules/taxonomy/migration_templates/d6_taxonomy_term.yml
index e5630fc..909fb4c 100644
--- a/core/modules/taxonomy/migration_templates/d6_taxonomy_term.yml
+++ b/core/modules/taxonomy/migration_templates/d6_taxonomy_term.yml
@@ -3,7 +3,7 @@ label: Taxonomy terms
 migration_tags:
   - Drupal 6
 source:
-  plugin: d6_taxonomy_term
+  plugin: taxonomy_term
 process:
   # If you are using this file to build a custom migration consider removing
   # the tid field to allow incremental migrations.
diff --git a/core/modules/taxonomy/migration_templates/d7_taxonomy_term.yml b/core/modules/taxonomy/migration_templates/d7_taxonomy_term.yml
index af85180..a47ab46 100644
--- a/core/modules/taxonomy/migration_templates/d7_taxonomy_term.yml
+++ b/core/modules/taxonomy/migration_templates/d7_taxonomy_term.yml
@@ -2,9 +2,8 @@ id: d7_taxonomy_term
 label: Taxonomy terms
 migration_tags:
   - Drupal 7
-deriver: Drupal\taxonomy\Plugin\migrate\D7TaxonomyTermDeriver
 source:
-  plugin: d7_taxonomy_term
+  plugin: taxonomy_term
 process:
   # If you are using this file to build a custom migration consider removing
   # the tid field to allow incremental migrations.
@@ -36,5 +35,3 @@ destination:
 migration_dependencies:
   required:
     - d7_taxonomy_vocabulary
-  optional:
-    - d7_field_instance
diff --git a/core/modules/taxonomy/src/Plugin/migrate/D7TaxonomyTermDeriver.php b/core/modules/taxonomy/src/Plugin/migrate/D7TaxonomyTermDeriver.php
deleted file mode 100644
index 22280ec..0000000
--- a/core/modules/taxonomy/src/Plugin/migrate/D7TaxonomyTermDeriver.php
+++ /dev/null
@@ -1,130 +0,0 @@
-<?php
-
-namespace Drupal\taxonomy\Plugin\migrate;
-
-use Drupal\Component\Plugin\Derivative\DeriverBase;
-use Drupal\Component\Plugin\Exception\PluginNotFoundException;
-use Drupal\Core\Database\DatabaseExceptionWrapper;
-use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
-use Drupal\migrate\Exception\RequirementsException;
-use Drupal\migrate\Plugin\Migration;
-use Drupal\migrate\Plugin\MigrationDeriverTrait;
-use Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Deriver for Drupal 7 taxonomy term migrations based on vocabularies.
- */
-class D7TaxonomyTermDeriver extends DeriverBase implements ContainerDeriverInterface {
-  use MigrationDeriverTrait;
-
-  /**
-   * The base plugin ID this derivative is for.
-   *
-   * @var string
-   */
-  protected $basePluginId;
-
-  /**
-   * Already-instantiated cckfield plugins, keyed by ID.
-   *
-   * @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldInterface[]
-   */
-  protected $cckPluginCache;
-
-  /**
-   * The CCK plugin manager.
-   *
-   * @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface
-   */
-  protected $cckPluginManager;
-
-  /**
-   * D7TaxonomyTermDeriver constructor.
-   *
-   * @param string $base_plugin_id
-   *   The base plugin ID for the plugin ID.
-   * @param \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface $cck_manager
-   *   The CCK plugin manager.
-   */
-  public function __construct($base_plugin_id, MigrateCckFieldPluginManagerInterface $cck_manager) {
-    $this->basePluginId = $base_plugin_id;
-    $this->cckPluginManager = $cck_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, $base_plugin_id) {
-    return new static(
-      $base_plugin_id,
-      $container->get('plugin.manager.migrate.cckfield')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDerivativeDefinitions($base_plugin_definition) {
-    $fields = [];
-    try {
-      $source_plugin = static::getSourcePlugin('d7_field_instance');
-      $source_plugin->checkRequirements();
-
-      // Read all field instance definitions in the source database.
-      foreach ($source_plugin as $row) {
-        if ($row->getSourceProperty('entity_type') == 'taxonomy_term') {
-          $fields[$row->getSourceProperty('bundle')][$row->getSourceProperty('field_name')] = $row->getSource();
-        }
-      }
-    }
-    catch (RequirementsException $e) {
-      // If checkRequirements() failed then the field module did not exist and
-      // we do not have any fields. Therefore, $fields will be empty and below
-      // we'll create a migration just for the node properties.
-    }
-
-    try {
-      foreach (static::getSourcePlugin('d7_taxonomy_vocabulary') as $row) {
-        $bundle = $row->getSourceProperty('machine_name');
-        $values = $base_plugin_definition;
-
-        $values['label'] = t('@label (@type)', [
-          '@label' => $values['label'],
-          '@type' => $row->getSourceProperty('name'),
-        ]);
-        $values['source']['bundle'] = $bundle;
-        $values['destination']['default_bundle'] = $bundle;
-
-        /** @var Migration $migration */
-        $migration = \Drupal::service('plugin.manager.migration')->createStubMigration($values);
-        if (isset($fields[$bundle])) {
-          foreach ($fields[$bundle] as $field_name => $info) {
-            $field_type = $info['type'];
-            try {
-              $plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, ['core' => 7], $migration);
-              if (!isset($this->cckPluginCache[$field_type])) {
-                $this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($plugin_id, ['core' => 7], $migration);
-              }
-              $this->cckPluginCache[$field_type]
-                ->processCckFieldValues($migration, $field_name, $info);
-            }
-            catch (PluginNotFoundException $ex) {
-              $migration->setProcessOfProperty($field_name, $field_name);
-            }
-          }
-        }
-        $this->derivatives[$bundle] = $migration->getPluginDefinition();
-      }
-    }
-    catch (DatabaseExceptionWrapper $e) {
-      // Once we begin iterating the source plugin it is possible that the
-      // source tables will not exist. This can happen when the
-      // MigrationPluginManager gathers up the migration definitions but we do
-      // not actually have a Drupal 7 source database.
-    }
-
-    return $this->derivatives;
-  }
-
-}
diff --git a/core/modules/taxonomy/src/Plugin/migrate/source/Term.php b/core/modules/taxonomy/src/Plugin/migrate/source/Term.php
index 14dda1f..0b2a5c3 100644
--- a/core/modules/taxonomy/src/Plugin/migrate/source/Term.php
+++ b/core/modules/taxonomy/src/Plugin/migrate/source/Term.php
@@ -14,10 +14,6 @@
  *   id = "taxonomy_term",
  *   source_provider = "taxonomy"
  * )
- *
- * @deprecated in Drupal 8.3.0, intended to be removed in Drupal 9.0.0.
- *   Use \Drupal\taxonomy\Plugin\migrate\source\d6\Term or
- *   \Drupal\taxonomy\Plugin\migrate\source\d7\Term.
  */
 class Term extends DrupalSqlBase {
 
diff --git a/core/modules/taxonomy/src/Plugin/migrate/source/d6/Term.php b/core/modules/taxonomy/src/Plugin/migrate/source/d6/Term.php
deleted file mode 100644
index 0418277..0000000
--- a/core/modules/taxonomy/src/Plugin/migrate/source/d6/Term.php
+++ /dev/null
@@ -1,74 +0,0 @@
-<?php
-
-namespace Drupal\taxonomy\Plugin\migrate\source\d6;
-
-use Drupal\migrate\Row;
-use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
-
-/**
- * Taxonomy term source from database.
- *
- * @todo Support term_relation, term_synonym table if possible.
- *
- * @MigrateSource(
- *   id = "d6_taxonomy_term",
- *   source_provider = "taxonomy"
- * )
- */
-class Term extends DrupalSqlBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function query() {
-    $query = $this->select('term_data', 'td')
-      ->fields('td')
-      ->distinct()
-      ->orderBy('td.tid');
-
-    if (isset($this->configuration['bundle'])) {
-      $query->condition('td.vid', $this->configuration['bundle'], 'IN');
-    }
-
-    return $query;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fields() {
-    $fields = [
-      'tid' => $this->t('The term ID.'),
-      'vid' => $this->t('Existing term VID'),
-      'name' => $this->t('The name of the term.'),
-      'description' => $this->t('The term description.'),
-      'weight' => $this->t('Weight'),
-      'parent' => $this->t("The Drupal term IDs of the term's parents."),
-    ];
-    return $fields;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function prepareRow(Row $row) {
-    // Find parents for this row.
-    $parents = $this->select('term_hierarchy', 'th')
-      ->fields('th', ['parent', 'tid'])
-      ->condition('tid', $row->getSourceProperty('tid'))
-      ->execute()
-      ->fetchCol();
-    $row->setSourceProperty('parent', $parents);
-
-    return parent::prepareRow($row);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getIds() {
-    $ids['tid']['type'] = 'integer';
-    return $ids;
-  }
-
-}
diff --git a/core/modules/taxonomy/src/Plugin/migrate/source/d7/Term.php b/core/modules/taxonomy/src/Plugin/migrate/source/d7/Term.php
deleted file mode 100644
index 518b9f7..0000000
--- a/core/modules/taxonomy/src/Plugin/migrate/source/d7/Term.php
+++ /dev/null
@@ -1,84 +0,0 @@
-<?php
-
-namespace Drupal\taxonomy\Plugin\migrate\source\d7;
-
-use Drupal\migrate\Row;
-use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
-
-/**
- * Taxonomy term source from database.
- *
- * @todo Support term_relation, term_synonym table if possible.
- *
- * @MigrateSource(
- *   id = "d7_taxonomy_term",
- *   source_provider = "taxonomy"
- * )
- */
-class Term extends FieldableEntity {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function query() {
-    $query = $this->select('taxonomy_term_data', 'td')
-      ->fields('td')
-      ->distinct()
-      ->orderBy('tid');
-    $query->leftJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
-    $query->addField('tv', 'machine_name');
-
-    if (isset($this->configuration['bundle'])) {
-      $query->condition('tv.machine_name', (array) $this->configuration['bundle'], 'IN');
-    }
-
-    return $query;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fields() {
-    $fields = [
-      'tid' => $this->t('The term ID.'),
-      'vid' => $this->t('Existing term VID'),
-      'machine_name' => $this->t('Vocabulary machine name'),
-      'name' => $this->t('The name of the term.'),
-      'description' => $this->t('The term description.'),
-      'weight' => $this->t('Weight'),
-      'parent' => $this->t("The Drupal term IDs of the term's parents."),
-      'format' => $this->t("Format of the term description."),
-    ];
-    return $fields;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function prepareRow(Row $row) {
-    // Get Field API field values.
-    foreach (array_keys($this->getFields('taxonomy_term', $row->getSourceProperty('machine_name'))) as $field) {
-      $tid = $row->getSourceProperty('tid');
-      $row->setSourceProperty($field, $this->getFieldValues('taxonomy_term', $field, $tid));
-    }
-
-    // Find parents for this row.
-    $parents = $this->select('taxonomy_term_hierarchy', 'th')
-      ->fields('th', ['parent', 'tid'])
-      ->condition('tid', $row->getSourceProperty('tid'))
-      ->execute()
-      ->fetchCol();
-    $row->setSourceProperty('parent', $parents);
-
-    return parent::prepareRow($row);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getIds() {
-    $ids['tid']['type'] = 'integer';
-    return $ids;
-  }
-
-}
diff --git a/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTest.php b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTest.php
index fbcc129..c7e7828 100644
--- a/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTest.php
+++ b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTest.php
@@ -13,16 +13,7 @@
  */
 class MigrateTaxonomyTermTest extends MigrateDrupal7TestBase {
 
-  public static $modules = [
-    'comment',
-    'datetime',
-    'image',
-    'link',
-    'node',
-    'taxonomy',
-    'telephone',
-    'text',
-  ];
+  public static $modules = array('taxonomy', 'text');
 
   /**
    * The cached taxonomy tree items, keyed by vid and tid.
@@ -37,16 +28,7 @@ class MigrateTaxonomyTermTest extends MigrateDrupal7TestBase {
   protected function setUp() {
     parent::setUp();
     $this->installEntitySchema('taxonomy_term');
-    $this->installConfig(static::$modules);
-
-    $this->executeMigrations([
-      'd7_node_type',
-      'd7_comment_type',
-      'd7_field',
-      'd7_taxonomy_vocabulary',
-      'd7_field_instance',
-      'd7_taxonomy_term'
-    ]);
+    $this->executeMigrations(['d7_taxonomy_vocabulary', 'd7_taxonomy_term']);
   }
 
   /**
@@ -66,12 +48,8 @@ protected function setUp() {
    *   The weight the migrated entity should have.
    * @param array $expected_parents
    *   The parent terms the migrated entity should have.
-   * @param int $expected_field_integer_value
-   *   The value the migrated entity field should have.
-   * @param int $expected_term_reference_tid
-   *   The term reference id the migrated entity field should have.
    */
-  protected function assertEntity($id, $expected_label, $expected_vid, $expected_description = '', $expected_format = NULL, $expected_weight = 0, $expected_parents = [], $expected_field_integer_value = NULL, $expected_term_reference_tid = NULL) {
+  protected function assertEntity($id, $expected_label, $expected_vid, $expected_description = '', $expected_format = NULL, $expected_weight = 0, $expected_parents = []) {
     /** @var \Drupal\taxonomy\TermInterface $entity */
     $entity = Term::load($id);
     $this->assertTrue($entity instanceof TermInterface);
@@ -82,14 +60,6 @@ protected function assertEntity($id, $expected_label, $expected_vid, $expected_d
     $this->assertEqual($expected_weight, $entity->getWeight());
     $this->assertIdentical($expected_parents, $this->getParentIDs($id));
     $this->assertHierarchy($expected_vid, $id, $expected_parents);
-    if (!is_null($expected_field_integer_value)) {
-      $this->assertTrue($entity->hasField('field_integer'));
-      $this->assertEquals($expected_field_integer_value, $entity->field_integer->value);
-    }
-    if (!is_null($expected_term_reference_tid)) {
-      $this->assertTrue($entity->hasField('field_integer'));
-      $this->assertEquals($expected_term_reference_tid, $entity->field_term_reference->target_id);
-    }
   }
 
   /**
@@ -97,9 +67,9 @@ protected function assertEntity($id, $expected_label, $expected_vid, $expected_d
    */
   public function testTaxonomyTerms() {
     $this->assertEntity(1, 'General discussion', 'forums', '', NULL, 2);
-    $this->assertEntity(2, 'Term1', 'test_vocabulary', 'The first term.', 'filtered_html', 0, [], NULL, 3);
+    $this->assertEntity(2, 'Term1', 'test_vocabulary', 'The first term.', 'filtered_html');
     $this->assertEntity(3, 'Term2', 'test_vocabulary', 'The second term.', 'filtered_html');
-    $this->assertEntity(4, 'Term3', 'test_vocabulary', 'The third term.', 'full_html', 0, [3], 6);
+    $this->assertEntity(4, 'Term3', 'test_vocabulary', 'The third term.', 'full_html', 0, [3]);
     $this->assertEntity(5, 'Custom Forum', 'forums', 'Where the cool kids are.', NULL, 3);
     $this->assertEntity(6, 'Games', 'forums', '', NULL, 4);
     $this->assertEntity(7, 'Minecraft', 'forums', '', NULL, 1, [6]);
diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/TermSourceWithVocabularyFilterTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermSourceWithVocabularyFilterTest.php
similarity index 87%
rename from core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/TermSourceWithVocabularyFilterTest.php
rename to core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermSourceWithVocabularyFilterTest.php
index 42fa75d..4ac2766 100644
--- a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/TermSourceWithVocabularyFilterTest.php
+++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermSourceWithVocabularyFilterTest.php
@@ -1,11 +1,11 @@
 <?php
 
-namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d6;
+namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source;
 
 /**
  * Tests the taxonomy term source with vocabulary filter.
  *
- * @covers \Drupal\taxonomy\Plugin\migrate\source\d6\Term
+ * @covers \Drupal\taxonomy\Plugin\migrate\source\Term
  * @group taxonomy
  */
 class TermSourceWithVocabularyFilterTest extends TermTest {
@@ -47,7 +47,7 @@ public function providerSource() {
 
     // Set up source plugin configuration.
     $tests[0]['configuration'] = [
-      'bundle' => [5],
+      'vocabulary' => [5],
     ];
 
     return $tests;
diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/TermTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermTest.php
similarity index 96%
rename from core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/TermTest.php
rename to core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermTest.php
index 29e23b8..bdffcf8 100644
--- a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/TermTest.php
+++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermTest.php
@@ -1,13 +1,13 @@
 <?php
 
-namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d6;
+namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source;
 
 use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
 
 /**
  * Tests taxonomy term source plugin.
  *
- * @covers \Drupal\taxonomy\Plugin\migrate\source\d6\Term
+ * @covers \Drupal\taxonomy\Plugin\migrate\source\Term
  * @group taxonomy
  */
 class TermTest extends MigrateSqlSourceTestBase {
diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermSourceWithVocabularyFilterTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermSourceWithVocabularyFilterTest.php
deleted file mode 100644
index e03a6ab..0000000
--- a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermSourceWithVocabularyFilterTest.php
+++ /dev/null
@@ -1,56 +0,0 @@
-<?php
-
-namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d7;
-
-/**
- * Tests the taxonomy term source with vocabulary filter.
- *
- * @covers \Drupal\taxonomy\Plugin\migrate\source\d7\Term
- * @group taxonomy
- */
-class TermSourceWithVocabularyFilterTest extends TermTest {
-
-  /**
-   * {@inheritdoc}
-   */
-  public static $modules = ['taxonomy', 'migrate_drupal'];
-
-  /**
-   * {@inheritdoc}
-   */
-  public function providerSource() {
-    // Get the source data from parent.
-    $tests = parent::providerSource();
-
-    // The expected results.
-    $tests[0]['expected_data'] = [
-      [
-        'tid' => 1,
-        'vid' => 5,
-        'name' => 'name value 1',
-        'description' => 'description value 1',
-        'weight' => 0,
-        'parent' => [0],
-      ],
-      [
-        'tid' => 4,
-        'vid' => 5,
-        'name' => 'name value 4',
-        'description' => 'description value 4',
-        'weight' => 1,
-        'parent' => [1],
-      ],
-    ];
-
-    // We know there are two rows with machine_name == 'tags'.
-    $tests[0]['expected_count'] = 2;
-
-    // Set up source plugin configuration.
-    $tests[0]['configuration'] = [
-      'bundle' => ['tags'],
-    ];
-
-    return $tests;
-  }
-
-}
diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermTest.php
deleted file mode 100644
index e20e4c0..0000000
--- a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermTest.php
+++ /dev/null
@@ -1,197 +0,0 @@
-<?php
-
-namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d7;
-
-use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
-
-/**
- * Tests taxonomy term source plugin.
- *
- * @covers \Drupal\taxonomy\Plugin\migrate\source\d7\Term
- * @group taxonomy
- */
-class TermTest extends MigrateSqlSourceTestBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public static $modules = ['taxonomy', 'migrate_drupal'];
-
-  /**
-   * {@inheritdoc}
-   */
-  public function providerSource() {
-    $tests = [];
-
-    // The source data.
-    $tests[0]['source_data']['taxonomy_term_data'] = [
-      [
-        'tid' => 1,
-        'vid' => 5,
-        'name' => 'name value 1',
-        'description' => 'description value 1',
-        'weight' => 0,
-      ],
-      [
-        'tid' => 2,
-        'vid' => 6,
-        'name' => 'name value 2',
-        'description' => 'description value 2',
-        'weight' => 0,
-      ],
-      [
-        'tid' => 3,
-        'vid' => 6,
-        'name' => 'name value 3',
-        'description' => 'description value 3',
-        'weight' => 0,
-      ],
-      [
-        'tid' => 4,
-        'vid' => 5,
-        'name' => 'name value 4',
-        'description' => 'description value 4',
-        'weight' => 1,
-      ],
-      [
-        'tid' => 5,
-        'vid' => 6,
-        'name' => 'name value 5',
-        'description' => 'description value 5',
-        'weight' => 1,
-      ],
-      [
-        'tid' => 6,
-        'vid' => 6,
-        'name' => 'name value 6',
-        'description' => 'description value 6',
-        'weight' => 0,
-      ],
-    ];
-    $tests[0]['source_data']['taxonomy_term_hierarchy'] = [
-      [
-        'tid' => 1,
-        'parent' => 0,
-      ],
-      [
-        'tid' => 2,
-        'parent' => 0,
-      ],
-      [
-        'tid' => 3,
-        'parent' => 0,
-      ],
-      [
-        'tid' => 4,
-        'parent' => 1,
-      ],
-      [
-        'tid' => 5,
-        'parent' => 2,
-      ],
-      [
-        'tid' => 6,
-        'parent' => 3,
-      ],
-      [
-        'tid' => 6,
-        'parent' => 2,
-      ],
-    ];
-    $tests[0]['source_data']['taxonomy_vocabulary'] = [
-      [
-        'vid' => 5,
-        'machine_name' => 'tags',
-      ],
-      [
-        'vid' => 6,
-        'machine_name' => 'categories',
-      ],
-    ];
-    $tests[0]['source_data']['field_config_instance'] = [
-      [
-        'field_name' => 'field_term_field',
-        'entity_type' => 'taxonomy_term',
-        'bundle' => 'tags',
-        'deleted' => 0,
-      ],
-      [
-        'field_name' => 'field_term_field',
-        'entity_type' => 'taxonomy_term',
-        'bundle' => 'categories',
-        'deleted' => 0,
-      ],
-    ];
-    $tests[0]['source_data']['field_data_field_term_field'] = [
-      [
-        'entity_type' => 'taxonomy_term',
-        'bundle' => 'tags',
-        'deleted' => 0,
-        'entity_id' => 1,
-        'delta' => 0,
-      ],
-      [
-        'entity_type' => 'taxonomy_term',
-        'bundle' => 'categories',
-        'deleted' => 0,
-        'entity_id' => 1,
-        'delta' => 0,
-      ],
-    ];
-
-    // The expected results.
-    $tests[0]['expected_data'] = [
-      [
-        'tid' => 1,
-        'vid' => 5,
-        'name' => 'name value 1',
-        'description' => 'description value 1',
-        'weight' => 0,
-        'parent' => [0],
-      ],
-      [
-        'tid' => 2,
-        'vid' => 6,
-        'name' => 'name value 2',
-        'description' => 'description value 2',
-        'weight' => 0,
-        'parent' => [0],
-      ],
-      [
-        'tid' => 3,
-        'vid' => 6,
-        'name' => 'name value 3',
-        'description' => 'description value 3',
-        'weight' => 0,
-        'parent' => [0],
-      ],
-      [
-        'tid' => 4,
-        'vid' => 5,
-        'name' => 'name value 4',
-        'description' => 'description value 4',
-        'weight' => 1,
-        'parent' => [1],
-      ],
-      [
-        'tid' => 5,
-        'vid' => 6,
-        'name' => 'name value 5',
-        'description' => 'description value 5',
-        'weight' => 1,
-        'parent' => [2],
-      ],
-      [
-        'tid' => 6,
-        'vid' => 6,
-        'name' => 'name value 6',
-        'description' => 'description value 6',
-        'weight' => 0,
-        'parent' => [3, 2],
-      ],
-    ];
-
-    return $tests;
-  }
-
-}
diff --git a/core/modules/telephone/telephone.info.yml b/core/modules/telephone/telephone.info.yml
index dbb0b1e..3e27ccb 100644
--- a/core/modules/telephone/telephone.info.yml
+++ b/core/modules/telephone/telephone.info.yml
@@ -1,6 +1,6 @@
 name: Telephone
 type: module
-description: 'Defines a field type that contains telephone numbers.'
+description: 'Defines a field type for telephone numbers.'
 package: Field types
 version: VERSION
 core: 8.x
diff --git a/core/modules/user/src/AccountForm.php b/core/modules/user/src/AccountForm.php
index 05257fd..81a98ae 100644
--- a/core/modules/user/src/AccountForm.php
+++ b/core/modules/user/src/AccountForm.php
@@ -2,12 +2,10 @@
 
 namespace Drupal\user;
 
-use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Entity\ContentEntityForm;
 use Drupal\Core\Entity\EntityConstraintViolationListInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
-use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
@@ -45,13 +43,9 @@
    *   The language manager.
    * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query
    *   The entity query factory.
-   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
-   *   The entity type bundle service.
-   * @param \Drupal\Component\Datetime\TimeInterface $time
-   *   The time service.
    */
-  public function __construct(EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, QueryFactory $entity_query, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
-    parent::__construct($entity_manager, $entity_type_bundle_info, $time);
+  public function __construct(EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, QueryFactory $entity_query) {
+    parent::__construct($entity_manager);
     $this->languageManager = $language_manager;
     $this->entityQuery = $entity_query;
   }
@@ -63,9 +57,7 @@ public static function create(ContainerInterface $container) {
     return new static(
       $container->get('entity.manager'),
       $container->get('language_manager'),
-      $container->get('entity.query'),
-      $container->get('entity_type.bundle.info'),
-      $container->get('datetime.time')
+      $container->get('entity.query')
     );
   }
 
diff --git a/core/modules/user/src/Controller/UserAuthenticationController.php b/core/modules/user/src/Controller/UserAuthenticationController.php
index 3aff75f..569a121 100644
--- a/core/modules/user/src/Controller/UserAuthenticationController.php
+++ b/core/modules/user/src/Controller/UserAuthenticationController.php
@@ -233,7 +233,7 @@ protected function userLoginFinalize(UserInterface $user) {
   /**
    * Logs out a user.
    *
-   * @return \Symfony\Component\HttpFoundation\Response
+   * @return \Drupal\rest\ResourceResponse
    *   The response object.
    */
   public function logout() {
diff --git a/core/modules/user/src/ProfileForm.php b/core/modules/user/src/ProfileForm.php
index fc80230..aee2e3a 100644
--- a/core/modules/user/src/ProfileForm.php
+++ b/core/modules/user/src/ProfileForm.php
@@ -2,7 +2,10 @@
 
 namespace Drupal\user;
 
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
 
 /**
  * Form handler for the profile forms.
@@ -12,6 +15,13 @@ class ProfileForm extends AccountForm {
   /**
    * {@inheritdoc}
    */
+  public function __construct(EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, QueryFactory $entity_query) {
+    parent::__construct($entity_manager, $language_manager, $entity_query);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   protected function actions(array $form, FormStateInterface $form_state) {
     $element = parent::actions($form, $form_state);
 
diff --git a/core/modules/views/tests/src/Unit/EntityViewsDataTest.php b/core/modules/views/tests/src/Unit/EntityViewsDataTest.php
index acb84e5..60ba8e5 100644
--- a/core/modules/views/tests/src/Unit/EntityViewsDataTest.php
+++ b/core/modules/views/tests/src/Unit/EntityViewsDataTest.php
@@ -89,12 +89,6 @@ protected function setUp() {
     $typed_data_manager->expects($this->any())
         ->method('createDataDefinition')
         ->willReturn($this->getMock('Drupal\Core\TypedData\DataDefinitionInterface'));
-
-    $typed_data_manager->expects($this->any())
-      ->method('getDefinition')
-      ->with($this->equalTo('field_item:string_long'))
-      ->willReturn(array('class' => '\Drupal\Core\Field\Plugin\Field\FieldType\StringLongItem'));
-
     $this->baseEntityType = new TestEntityType([
       'base_table' => 'entity_test',
       'id' => 'entity_test',
@@ -1113,12 +1107,3 @@ function t($string, array $args = []) {
     return strtr($string, $args);
   }
 }
-
-
-namespace Drupal\Core\Entity;
-
-if (!function_exists('t')) {
-  function t($string, array $args = []) {
-    return strtr($string, $args);
-  }
-}
diff --git a/core/modules/views/views.info.yml b/core/modules/views/views.info.yml
index b91a35f..9e9b384 100644
--- a/core/modules/views/views.info.yml
+++ b/core/modules/views/views.info.yml
@@ -1,6 +1,6 @@
 name: Views
 type: module
-description: 'Provides a back end to fetch information from the database and to display it in different formats.'
+description: 'Create customized lists and queries from your database.'
 package: Core
 version: VERSION
 core: 8.x
diff --git a/core/modules/views_ui/views_ui.info.yml b/core/modules/views_ui/views_ui.info.yml
index 618c08f..71770b8 100644
--- a/core/modules/views_ui/views_ui.info.yml
+++ b/core/modules/views_ui/views_ui.info.yml
@@ -1,6 +1,6 @@
 name: 'Views UI'
 type: module
-description: 'Provides an interface for managing views.'
+description: 'Administrative interface for Views.'
 package: Core
 version: VERSION
 core: 8.x
diff --git a/core/modules/workflows/config/schema/workflows.schema.yml b/core/modules/workflows/config/schema/workflows.schema.yml
deleted file mode 100644
index e19eb6b..0000000
--- a/core/modules/workflows/config/schema/workflows.schema.yml
+++ /dev/null
@@ -1,51 +0,0 @@
-workflows.workflow.*:
-  type: config_entity
-  label: 'Workflow'
-  mapping:
-    id:
-      type: string
-      label: 'ID'
-    label:
-      type: label
-      label: 'Label'
-    type:
-      type: string
-      label: 'Workflow type'
-    type_settings:
-      type: workflow.type_settings.[%parent.type]
-      label: 'Custom settings for workflow type'
-    states:
-      type: sequence
-      label: 'States'
-      sequence:
-        type: mapping
-        label: 'State'
-        mapping:
-          label:
-            type: label
-            label: 'Label'
-          weight:
-            type: integer
-            label: 'Weight'
-    transitions:
-      type: sequence
-      label: 'Transitions'
-      sequence:
-        type: mapping
-        label: 'Transition from state to state'
-        mapping:
-          label:
-            type: label
-            label: 'Transition label'
-          from:
-            type: sequence
-            label: 'From state IDs'
-            sequence:
-              type: string
-              label: 'From state ID'
-          to:
-            type: string
-            label: 'To state ID'
-          weight:
-            type: integer
-            label: 'Weight'
diff --git a/core/modules/workflows/src/Annotation/WorkflowType.php b/core/modules/workflows/src/Annotation/WorkflowType.php
deleted file mode 100644
index 2aa3ff9..0000000
--- a/core/modules/workflows/src/Annotation/WorkflowType.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-namespace Drupal\workflows\Annotation;
-
-use Drupal\Component\Annotation\Plugin;
-
-/**
- * Defines an Workflow type annotation object.
- *
- * Plugin Namespace: Plugin\WorkflowType
- *
- * For a working example, see \Drupal\content_moderation\Plugin\Workflow\ContentModerate
- *
- * @see \Drupal\workflows\WorkflowTypeInterface
- * @see \Drupal\workflows\WorkflowManager
- * @see plugin_api
- *
- * @Annotation
- *
- * @internal
- *   The workflow system is currently experimental and should only be leveraged
- *   by experimental modules and development releases of contributed modules.
- */
-class WorkflowType extends Plugin {
-
-  /**
-   * The plugin ID.
-   *
-   * @var string
-   */
-  public $id;
-
-  /**
-   * The label of the workflow.
-   *
-   * Describes how the plugin is used to apply a workflow to something.
-   *
-   * @var \Drupal\Core\Annotation\Translation
-   *
-   * @ingroup plugin_translatable
-   */
-  public $label = '';
-
-}
diff --git a/core/modules/workflows/src/Entity/Workflow.php b/core/modules/workflows/src/Entity/Workflow.php
deleted file mode 100644
index 3bf8d22..0000000
--- a/core/modules/workflows/src/Entity/Workflow.php
+++ /dev/null
@@ -1,506 +0,0 @@
-<?php
-
-namespace Drupal\workflows\Entity;
-
-use Drupal\Core\Config\Entity\ConfigEntityBase;
-use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
-use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
-use Drupal\workflows\State;
-use Drupal\workflows\Transition;
-use Drupal\workflows\WorkflowInterface;
-
-/**
- * Defines the workflow entity.
- *
- * @ConfigEntityType(
- *   id = "workflow",
- *   label = @Translation("Workflow"),
- *   handlers = {
- *     "access" = "Drupal\workflows\WorkflowAccessControlHandler",
- *     "route_provider" = {
- *       "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
- *     },
- *   },
- *   config_prefix = "workflow",
- *   entity_keys = {
- *     "id" = "id",
- *     "label" = "label",
- *     "uuid" = "uuid",
- *   },
- *   config_export = {
- *     "id",
- *     "label",
- *     "states",
- *     "transitions",
- *     "type",
- *     "type_settings"
- *   },
- * )
- *
- * @internal
- *   The workflow system is currently experimental and should only be leveraged
- *   by experimental modules and development releases of contributed modules.
- */
-class Workflow extends ConfigEntityBase implements WorkflowInterface, EntityWithPluginCollectionInterface {
-
-  /**
-   * The Workflow ID.
-   *
-   * @var string
-   */
-  protected $id;
-
-  /**
-   * The Moderation state label.
-   *
-   * @var string
-   */
-  protected $label;
-
-  /**
-   * The states of the workflow.
-   *
-   * The array key is the machine name for the state. The structure of each
-   * array item is:
-   * @code
-   *   label: {translatable label}
-   *   weight: {integer value}
-   * @endcode
-   *
-   * @var array
-   */
-  protected $states = [];
-
-  /**
-   * The permitted transitions of the workflow.
-   *
-   * The array key is the machine name for the transition. The machine name is
-   * generated from the machine names of the states. The structure of each array
-   * item is:
-   * @code
-   *   from:
-   *     - {state machine name}
-   *     - {state machine name}
-   *   to: {state machine name}
-   *   label: {translatable label}
-   * @endcode
-   *
-   * @var array
-   */
-  protected $transitions = [];
-
-  /**
-   * The workflow type plugin ID.
-   *
-   * @see \Drupal\workflows\WorkflowTypeManager
-   *
-   * @var string
-   */
-  protected $type;
-
-  /**
-   * The configuration for the workflow type plugin.
-   * @var array
-   */
-  protected $type_settings = [];
-
-  /**
-   * The workflow type plugin collection.
-   *
-   * @var \Drupal\Component\Plugin\LazyPluginCollection
-   */
-  protected $pluginCollection;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function addState($state_id, $label) {
-    if (isset($this->states[$state_id])) {
-      throw new \InvalidArgumentException("The state '$state_id' already exists in workflow '{$this->id()}'");
-    }
-    if (preg_match('/[^a-z0-9_]+/', $state_id)) {
-      throw new \InvalidArgumentException("The state ID '$state_id' must contain only lowercase letters, numbers, and underscores");
-    }
-    $this->states[$state_id] = [
-      'label' => $label,
-      'weight' => $this->getNextWeight($this->states),
-    ];
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function hasState($state_id) {
-    return isset($this->states[$state_id]);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getStates($state_ids = NULL) {
-    if ($state_ids === NULL) {
-      $state_ids = array_keys($this->states);
-    }
-    /** @var \Drupal\workflows\StateInterface[] $states */
-    $states = array_combine($state_ids, array_map([$this, 'getState'], $state_ids));
-    if (count($states) > 1) {
-      // Sort states by weight and then label.
-      $weights = $labels = [];
-      foreach ($states as $id => $state) {
-        $weights[$id] = $state->weight();
-        $labels[$id] = $state->label();
-      }
-      array_multisort(
-        $weights, SORT_NUMERIC, SORT_ASC,
-        $labels, SORT_NATURAL, SORT_ASC
-      );
-      $states = array_replace($weights, $states);
-    }
-    return $states;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getState($state_id) {
-    if (!isset($this->states[$state_id])) {
-      throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow '{$this->id()}'");
-    }
-    $state = new State(
-      $this,
-      $state_id,
-      $this->states[$state_id]['label'],
-      $this->states[$state_id]['weight']
-    );
-    return $this->getTypePlugin()->decorateState($state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setStateLabel($state_id, $label) {
-    if (!isset($this->states[$state_id])) {
-      throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow '{$this->id()}'");
-    }
-    $this->states[$state_id]['label'] = $label;
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setStateWeight($state_id, $weight) {
-    if (!isset($this->states[$state_id])) {
-      throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow '{$this->id()}'");
-    }
-    $this->states[$state_id]['weight'] = $weight;
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function deleteState($state_id) {
-    if (!isset($this->states[$state_id])) {
-      throw new \InvalidArgumentException("The state '$state_id' does not exist in workflow '{$this->id()}'");
-    }
-    if (count($this->states) === 1) {
-      throw new \InvalidArgumentException("The state '$state_id' can not be deleted from workflow '{$this->id()}' as it is the only state");
-    }
-
-    foreach ($this->transitions as $transition_id => $transition) {
-      $from_key = array_search($state_id, $transition['from'], TRUE);
-      if ($from_key !== FALSE) {
-        // Remove state from the from array.
-        unset($transition['from'][$from_key]);
-      }
-      if (empty($transition['from']) || $transition['to'] === $state_id) {
-        $this->deleteTransition($transition_id);
-      }
-      elseif ($from_key !== FALSE) {
-        $this->setTransitionFromStates($transition_id, $transition['from']);
-      }
-    }
-    unset($this->states[$state_id]);
-    $this->getTypePlugin()->deleteState($state_id);
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getInitialState() {
-    $ordered_states = $this->getStates();
-    return reset($ordered_states);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function addTransition($transition_id, $label, array $from_state_ids, $to_state_id) {
-    if (isset($this->transitions[$transition_id])) {
-      throw new \InvalidArgumentException("The transition '$transition_id' already exists in workflow '{$this->id()}'");
-    }
-    if (preg_match('/[^a-z0-9_]+/', $transition_id)) {
-      throw new \InvalidArgumentException("The transition ID '$transition_id' must contain only lowercase letters, numbers, and underscores");
-    }
-
-    if (!$this->hasState($to_state_id)) {
-      throw new \InvalidArgumentException("The state '$to_state_id' does not exist in workflow '{$this->id()}'");
-    }
-    $this->transitions[$transition_id] = [
-      'label' => $label,
-      'from' => [],
-      'to' => $to_state_id,
-      // Always add to the end.
-      'weight' => $this->getNextWeight($this->transitions),
-    ];
-
-    try {
-      $this->setTransitionFromStates($transition_id, $from_state_ids);
-    }
-    catch (\InvalidArgumentException $e) {
-      unset($this->transitions[$transition_id]);
-      throw $e;
-    }
-
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getTransitions(array $transition_ids = NULL) {
-    if ($transition_ids === NULL) {
-      $transition_ids = array_keys($this->transitions);
-    }
-    /** @var \Drupal\workflows\TransitionInterface[] $transitions */
-    $transitions = array_combine($transition_ids, array_map([$this, 'getTransition'], $transition_ids));
-    if (count($transitions) > 1) {
-      // Sort transitions by weights and then labels.
-      $weights = $labels = [];
-      foreach ($transitions as $id => $transition) {
-        $weights[$id] = $transition->weight();
-        $labels[$id] = $transition->label();
-      }
-      array_multisort(
-        $weights, SORT_NUMERIC, SORT_ASC,
-        $labels, SORT_NATURAL, SORT_ASC
-      );
-      $transitions = array_replace($weights, $transitions);
-    }
-    return $transitions;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getTransition($transition_id) {
-    if (!isset($this->transitions[$transition_id])) {
-      throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow '{$this->id()}'");
-    }
-    $transition = new Transition(
-      $this,
-      $transition_id,
-      $this->transitions[$transition_id]['label'],
-      $this->transitions[$transition_id]['from'],
-      $this->transitions[$transition_id]['to'],
-      $this->transitions[$transition_id]['weight']
-    );
-    return $this->getTypePlugin()->decorateTransition($transition);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function hasTransition($transition_id) {
-    return isset($this->transitions[$transition_id]);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getTransitionsForState($state_id, $direction = 'from') {
-    $transition_ids = array_keys(array_filter($this->transitions, function ($transition) use ($state_id, $direction) {
-      return in_array($state_id, (array) $transition[$direction], TRUE);
-    }));
-    return $this->getTransitions($transition_ids);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getTransitionFromStateToState($from_state_id, $to_state_id) {
-    $transition_id = $this->getTransitionIdFromStateToState($from_state_id, $to_state_id);
-    if (empty($transition_id)) {
-      throw new \InvalidArgumentException("The transition from '$from_state_id' to '$to_state_id' does not exist in workflow '{$this->id()}'");
-    }
-    return $this->getTransition($transition_id);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function hasTransitionFromStateToState($from_state_id, $to_state_id) {
-    return !empty($this->getTransitionIdFromStateToState($from_state_id, $to_state_id));
-  }
-
-  /**
-   * Gets the transition ID from state to state.
-   *
-   * @param string $from_state_id
-   *   The state ID to transition from.
-   * @param string $to_state_id
-   *   The state ID to transition to.
-   *
-   * @return string|null
-   *   The transition ID, or NULL if no transition exists.
-   */
-  protected function getTransitionIdFromStateToState($from_state_id, $to_state_id) {
-    foreach ($this->transitions as $transition_id => $transition) {
-      if (in_array($from_state_id, $transition['from'], TRUE) && $transition['to'] === $to_state_id) {
-        return $transition_id;
-      }
-    }
-    return NULL;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setTransitionLabel($transition_id, $label) {
-    if (isset($this->transitions[$transition_id])) {
-      $this->transitions[$transition_id]['label'] = $label;
-    }
-    else {
-      throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow '{$this->id()}'");
-    }
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setTransitionWeight($transition_id, $weight) {
-    if (isset($this->transitions[$transition_id])) {
-      $this->transitions[$transition_id]['weight'] = $weight;
-    }
-    else {
-      throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow '{$this->id()}'");
-    }
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setTransitionFromStates($transition_id, array $from_state_ids) {
-    if (!isset($this->transitions[$transition_id])) {
-      throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow '{$this->id()}'");
-    }
-
-    // Ensure that the states exist.
-    foreach ($from_state_ids as $from_state_id) {
-      if (!$this->hasState($from_state_id)) {
-        throw new \InvalidArgumentException("The state '$from_state_id' does not exist in workflow '{$this->id()}'");
-      }
-      if ($this->hasTransitionFromStateToState($from_state_id, $this->transitions[$transition_id]['to'])) {
-        $transition = $this->getTransitionFromStateToState($from_state_id, $this->transitions[$transition_id]['to']);
-        if ($transition_id !== $transition->id()) {
-          throw new \InvalidArgumentException("The '{$transition->id()}' transition already allows '$from_state_id' to '{$this->transitions[$transition_id]['to']}' transitions in workflow '{$this->id()}'");
-        }
-      }
-    }
-
-    // Preserve the order of the state IDs in the from value and don't save any
-    // keys.
-    $from_state_ids = array_values($from_state_ids);
-    sort($from_state_ids);
-    $this->transitions[$transition_id]['from'] = $from_state_ids;
-
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function deleteTransition($transition_id) {
-    if (isset($this->transitions[$transition_id])) {
-      unset($this->transitions[$transition_id]);
-      $this->getTypePlugin()->deleteTransition($transition_id);
-    }
-    else {
-      throw new \InvalidArgumentException("The transition '$transition_id' does not exist in workflow '{$this->id()}'");
-    }
-    return $this;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public function getTypePlugin() {
-    return $this->getPluginCollection()->get($this->type);
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public function getPluginCollections() {
-    return ['type_settings' => $this->getPluginCollection()];
-  }
-
-  /**
-   * Encapsulates the creation of the workflow's plugin collection.
-   *
-   * @return \Drupal\Core\Plugin\DefaultSingleLazyPluginCollection
-   *   The workflow's plugin collection.
-   */
-  protected function getPluginCollection() {
-    if (!$this->pluginCollection && $this->type) {
-      $this->pluginCollection = new DefaultSingleLazyPluginCollection(\Drupal::service('plugin.manager.workflows.type'), $this->type, $this->type_settings);
-    }
-    return $this->pluginCollection;
-  }
-
-  /**
-   * Loads all workflows of the provided type.
-   *
-   * @param string $type
-   *   The workflow type to load all workflows for.
-   *
-   * @return static[]
-   *   An array of workflow objects of the provided workflow type, indexed by
-   *   their IDs.
-   *
-   *  @see \Drupal\workflows\Annotation\WorkflowType
-   */
-  public static function loadMultipleByType($type) {
-    return self::loadMultiple(\Drupal::entityQuery('workflow')->condition('type', $type)->execute());
-  }
-
-  /**
-   * Gets the weight for a new state or transition.
-   *
-   * @param array $items
-   *   An array of states or transitions information where each item has a
-   *   'weight' key with a numeric value.
-   *
-   * @return int
-   *   The weight for a new item in the array so that it has the highest weight.
-   */
-  protected function getNextWeight(array $items) {
-    return array_reduce($items, function ($carry, $item) {
-      return max($carry, $item['weight'] + 1);
-    }, 0);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function status() {
-    // In order for a workflow to be usable it must have at least one state.
-    return !empty($this->status) && !empty($this->states);
-  }
-
-}
diff --git a/core/modules/workflows/src/Form/WorkflowAddForm.php b/core/modules/workflows/src/Form/WorkflowAddForm.php
deleted file mode 100644
index c779b8f..0000000
--- a/core/modules/workflows/src/Form/WorkflowAddForm.php
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-
-namespace Drupal\workflows\Form;
-
-use Drupal\Component\Plugin\PluginManagerInterface;
-use Drupal\workflows\Entity\Workflow;
-use Drupal\Core\Entity\EntityForm;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Form for adding workflows.
- */
-class WorkflowAddForm extends EntityForm {
-
-  /**
-   * The workflow type plugin manager.
-   *
-   * @var \Drupal\Component\Plugin\PluginManagerInterface
-   */
-  protected $workflowTypePluginManager;
-
-  /**
-   * WorkflowAddForm constructor.
-   *
-   * @param \Drupal\Component\Plugin\PluginManagerInterface $workflow_type_plugin_manager
-   *   The workflow type plugin manager.
-   */
-  public function __construct(PluginManagerInterface $workflow_type_plugin_manager) {
-    $this->workflowTypePluginManager = $workflow_type_plugin_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('plugin.manager.workflows.type')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function form(array $form, FormStateInterface $form_state) {
-    $form = parent::form($form, $form_state);
-
-    /* @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->entity;
-    $form['label'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Label'),
-      '#maxlength' => 255,
-      '#default_value' => $workflow->label(),
-      '#description' => $this->t('Label for the Workflow.'),
-      '#required' => TRUE,
-    ];
-
-    $form['id'] = [
-      '#type' => 'machine_name',
-      '#default_value' => $workflow->id(),
-      '#machine_name' => [
-        'exists' => [Workflow::class, 'load'],
-      ],
-    ];
-
-    $workflow_types = array_map(function ($plugin_definition) {
-      return $plugin_definition['label'];
-    }, $this->workflowTypePluginManager->getDefinitions());
-    $form['workflow_type'] = [
-      '#type' => 'select',
-      '#title' => $this->t('Workflow type'),
-      '#required' => TRUE,
-      '#options' => $workflow_types,
-    ];
-
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function save(array $form, FormStateInterface $form_state) {
-    /* @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->entity;
-    $workflow->save();
-    drupal_set_message($this->t('Created the %label Workflow. In order for the workflow to be enabled there needs to be at least one state.', [
-      '%label' => $workflow->label(),
-    ]));
-    $form_state->setRedirectUrl($workflow->toUrl('add-state-form'));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
-    // This form can only set the workflow's ID, label and the weights for each
-    // state.
-    /** @var \Drupal\workflows\WorkflowInterface $entity */
-    $values = $form_state->getValues();
-    $entity->set('label', $values['label']);
-    $entity->set('id', $values['id']);
-    $entity->set('type', $values['workflow_type']);
-  }
-
-}
diff --git a/core/modules/workflows/src/Form/WorkflowEditForm.php b/core/modules/workflows/src/Form/WorkflowEditForm.php
deleted file mode 100644
index d8f99f9..0000000
--- a/core/modules/workflows/src/Form/WorkflowEditForm.php
+++ /dev/null
@@ -1,214 +0,0 @@
-<?php
-
-namespace Drupal\workflows\Form;
-
-use Drupal\workflows\Entity\Workflow;
-use Drupal\workflows\State;
-use Drupal\Core\Entity\EntityForm;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Url;
-
-/**
- * The form for editing workflows.
- */
-class WorkflowEditForm extends EntityForm {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function form(array $form, FormStateInterface $form_state) {
-    $form = parent::form($form, $form_state);
-
-    /* @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->entity;
-    $form['label'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Label'),
-      '#maxlength' => 255,
-      '#default_value' => $workflow->label(),
-      '#description' => $this->t('Label for the Workflow.'),
-      '#required' => TRUE,
-    ];
-
-    $form['id'] = [
-      '#type' => 'machine_name',
-      '#default_value' => $workflow->id(),
-      '#machine_name' => [
-        'exists' => [Workflow::class, 'load'],
-      ],
-      '#disabled' => TRUE,
-    ];
-
-    $header = [
-      'state' => $this->t('State'),
-      'weight' => $this->t('Weight'),
-      'operations' => $this->t('Operations')
-    ];
-    $form['states_container'] = [
-      '#type' => 'details',
-      '#title' => $this->t('States'),
-      '#open' => TRUE,
-      '#collapsible' => 'FALSE',
-    ];
-    $form['states_container']['states'] = [
-      '#type' => 'table',
-      '#header' => $header,
-      '#title' => $this->t('States'),
-      '#empty' => $this->t('There are no states yet.'),
-      '#tabledrag' => [
-        [
-          'action' => 'order',
-          'relationship' => 'sibling',
-          'group' => 'state-weight',
-        ],
-      ],
-    ];
-
-    $states = $workflow->getStates();
-
-    // Warn the user if there are no states.
-    if (empty($states)) {
-      drupal_set_message(
-        $this->t(
-          'This workflow has no states and will be disabled until there is at least one, <a href=":add-state">add a new state.</a>',
-          [':add-state' => $workflow->toUrl('add-state-form')->toString()]
-        ),
-        'warning'
-      );
-    }
-
-    $delete_state_access = $this->entity->access('delete-state');
-    foreach ($states as $state) {
-      $links['edit'] = [
-        'title' => $this->t('Edit'),
-        'url' => Url::fromRoute('entity.workflow.edit_state_form', ['workflow' => $workflow->id(), 'workflow_state' => $state->id()]),
-        'attributes' => ['aria-label' => $this->t('Edit @state state', ['@state' => $state->label()])],
-      ];
-      if ($delete_state_access) {
-        $links['delete'] = [
-          'title' => t('Delete'),
-          'url' => Url::fromRoute('entity.workflow.delete_state_form', [
-            'workflow' => $workflow->id(),
-            'workflow_state' => $state->id()
-          ]),
-          'attributes' => ['aria-label' => $this->t('Delete @state state', ['@state' => $state->label()])],
-        ];
-      }
-      $form['states_container']['states'][$state->id()] = [
-        '#attributes' => ['class' => ['draggable']],
-        'state' => ['#markup' => $state->label()],
-        '#weight' => $state->weight(),
-        'weight' => [
-          '#type' => 'weight',
-          '#title' => t('Weight for @title', ['@title' => $state->label()]),
-          '#title_display' => 'invisible',
-          '#default_value' => $state->weight(),
-          '#attributes' => ['class' => ['state-weight']],
-        ],
-        'operations' => [
-          '#type' => 'operations',
-          '#links' => $links,
-        ],
-      ];
-    }
-    $form['states_container']['state_add'] = [
-      '#markup' => $workflow->toLink($this->t('Add a new state'), 'add-state-form')->toString(),
-    ];
-
-    $header = [
-      'label' => $this->t('Label'),
-      'weight' => $this->t('Weight'),
-      'from' => $this->t('From'),
-      'to' => $this->t('To'),
-      'operations' => $this->t('Operations')
-    ];
-    $form['transitions_container'] = [
-      '#type' => 'details',
-      '#title' => $this->t('Transitions'),
-      '#open' => TRUE,
-    ];
-    $form['transitions_container']['transitions'] = [
-      '#type' => 'table',
-      '#header' => $header,
-      '#title' => $this->t('Transitions'),
-      '#empty' => $this->t('There are no transitions yet.'),
-      '#tabledrag' => [
-        [
-          'action' => 'order',
-          'relationship' => 'sibling',
-          'group' => 'transition-weight',
-        ],
-      ],
-    ];
-    foreach ($workflow->getTransitions() as $transition) {
-      $links['edit'] = [
-        'title' => $this->t('Edit'),
-        'url' => Url::fromRoute('entity.workflow.edit_transition_form', ['workflow' => $workflow->id(), 'workflow_transition' => $transition->id()]),
-        'attributes' => ['aria-label' => $this->t('Edit \'@transition\' transition', ['@transition' => $transition->label()])],
-      ];
-      $links['delete'] = [
-        'title' => t('Delete'),
-        'url' => Url::fromRoute('entity.workflow.delete_transition_form', ['workflow' => $workflow->id(), 'workflow_transition' => $transition->id()]),
-        'attributes' => ['aria-label' => $this->t('Delete \'@transition\' transition', ['@transition' => $transition->label()])],
-      ];
-      $form['transitions_container']['transitions'][$transition->id()] = [
-        '#attributes' => ['class' => ['draggable']],
-        'label' => ['#markup' => $transition->label()],
-        '#weight' => $transition->weight(),
-        'weight' => [
-          '#type' => 'weight',
-          '#title' => t('Weight for @title', ['@title' => $transition->label()]),
-          '#title_display' => 'invisible',
-          '#default_value' => $transition->weight(),
-          '#attributes' => ['class' => ['transition-weight']],
-        ],
-        'from' => [
-          '#theme' => 'item_list',
-          '#items' => array_map([State::class, 'labelCallback'], $transition->from()),
-          '#context' => ['list_style' => 'comma-list'],
-        ],
-        'to' => ['#markup' => $transition->to()->label()],
-        'operations' => [
-          '#type' => 'operations',
-          '#links' => $links,
-        ],
-      ];
-    }
-    $form['transitions_container']['transition_add'] = [
-      '#markup' => $workflow->toLink($this->t('Add a new transition'), 'add-transition-form')->toString(),
-    ];
-
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function save(array $form, FormStateInterface $form_state) {
-    /* @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->entity;
-    $workflow->save();
-    drupal_set_message($this->t('Saved the %label Workflow.', ['%label' => $workflow->label()]));
-    $form_state->setRedirectUrl($workflow->toUrl('collection'));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
-    // This form can only set the workflow's ID, label and the weights for each
-    // state.
-    /** @var \Drupal\workflows\WorkflowInterface $entity */
-    $values = $form_state->getValues();
-    $entity->set('label', $values['label']);
-    $entity->set('id', $values['id']);
-    foreach ($values['states'] as $state_id => $state_values) {
-      $entity->setStateWeight($state_id, $state_values['weight']);
-    }
-    foreach ($values['transitions'] as $transition_id => $transition_values) {
-      $entity->setTransitionWeight($transition_id, $transition_values['weight']);
-    }
-  }
-
-}
diff --git a/core/modules/workflows/src/Form/WorkflowStateAddForm.php b/core/modules/workflows/src/Form/WorkflowStateAddForm.php
deleted file mode 100644
index c9b46e2..0000000
--- a/core/modules/workflows/src/Form/WorkflowStateAddForm.php
+++ /dev/null
@@ -1,115 +0,0 @@
-<?php
-
-namespace Drupal\workflows\Form;
-
-use Drupal\Core\Entity\EntityForm;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Form\FormStateInterface;
-
-/**
- * Class WorkflowStateAddForm.
- */
-class WorkflowStateAddForm extends EntityForm {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function form(array $form, FormStateInterface $form_state) {
-    $form = parent::form($form, $form_state);
-
-    /* @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->getEntity();
-    $form['label'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Label'),
-      '#maxlength' => 255,
-      '#default_value' => '',
-      '#description' => $this->t('Label for the state.'),
-      '#required' => TRUE,
-    ];
-
-    $form['id'] = [
-      '#type' => 'machine_name',
-      '#machine_name' => [
-        'exists' => [$this, 'exists'],
-      ],
-    ];
-
-    // Add additional form fields from the workflow type plugin.
-    $form['type_settings'] = [
-      $workflow->get('type') => $workflow->getTypePlugin()->buildStateConfigurationForm($form_state, $workflow),
-      '#tree' => TRUE,
-    ];
-
-    return $form;
-  }
-
-  /**
-   * Determines if the workflow state already exists.
-   *
-   * @param string $state_id
-   *   The workflow state ID.
-   *
-   * @return bool
-   *   TRUE if the workflow state exists, FALSE otherwise.
-   */
-  public function exists($state_id) {
-    /** @var \Drupal\workflows\WorkflowInterface $original_workflow */
-    $original_workflow = \Drupal::entityTypeManager()->getStorage('workflow')->loadUnchanged($this->getEntity()->id());
-    return $original_workflow->hasState($state_id);
-  }
-
-  /**
-   * Copies top-level form values to entity properties
-   *
-   * This form can only change values for a state, which is part of workflow.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity the current form should operate upon.
-   * @param array $form
-   *   A nested array of form elements comprising the form.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   */
-  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
-    /** @var \Drupal\workflows\WorkflowInterface $entity */
-    $values = $form_state->getValues();
-
-    // This is fired twice so we have to check that the entity does not already
-    // have the state.
-    if (!$entity->hasState($values['id'])) {
-      $entity->addState($values['id'], $values['label']);
-      if (isset($values['type_settings'])) {
-        $configuration = $entity->getTypePlugin()->getConfiguration();
-        $configuration['states'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()->getPluginId()];
-        $entity->set('type_settings', $configuration);
-      }
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function save(array $form, FormStateInterface $form_state) {
-    /** @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->entity;
-    $workflow->save();
-    drupal_set_message($this->t('Created %label state.', [
-      '%label' => $workflow->getState($form_state->getValue('id'))->label(),
-    ]));
-    $form_state->setRedirectUrl($workflow->toUrl('edit-form'));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function actions(array $form, FormStateInterface $form_state) {
-    $actions['submit'] = [
-      '#type' => 'submit',
-      '#value' => $this->t('Save'),
-      '#submit' => ['::submitForm', '::save'],
-    ];
-    return $actions;
-  }
-
-}
diff --git a/core/modules/workflows/src/Form/WorkflowStateDeleteForm.php b/core/modules/workflows/src/Form/WorkflowStateDeleteForm.php
deleted file mode 100644
index c60045c..0000000
--- a/core/modules/workflows/src/Form/WorkflowStateDeleteForm.php
+++ /dev/null
@@ -1,99 +0,0 @@
-<?php
-
-namespace Drupal\workflows\Form;
-
-use Drupal\workflows\WorkflowInterface;
-use Drupal\Core\Form\ConfirmFormBase;
-use Drupal\Core\Form\FormStateInterface;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
-
-/**
- * Builds the form to delete states from Workflow entities.
- */
-class WorkflowStateDeleteForm extends ConfirmFormBase {
-
-  /**
-   * The workflow entity the state being deleted belongs to.
-   *
-   * @var \Drupal\workflows\WorkflowInterface
-   */
-  protected $workflow;
-
-  /**
-   * The state being deleted.
-   *
-   * @var string
-   */
-  protected $stateId;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'workflow_state_delete_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getQuestion() {
-    return $this->t('Are you sure you want to delete %state from %workflow?', ['%state' => $this->workflow->getState($this->stateId)->label(), '%workflow' => $this->workflow->label()]);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getCancelUrl() {
-    return $this->workflow->toUrl();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getConfirmText() {
-    return $this->t('Delete');
-  }
-
-  /**
-   * Form constructor.
-   *
-   * @param array $form
-   *   An associative array containing the structure of the form.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   * @param \Drupal\workflows\WorkflowInterface $workflow
-   *   The workflow entity being edited.
-   * @param string|null $workflow_state
-   *   The workflow state being deleted.
-   *
-   * @return array
-   *   The form structure.
-   */
-  public function buildForm(array $form, FormStateInterface $form_state, WorkflowInterface $workflow = NULL, $workflow_state = NULL) {
-    if (!$workflow->hasState($workflow_state)) {
-      throw new NotFoundHttpException();
-    }
-    $this->workflow = $workflow;
-    $this->stateId = $workflow_state;
-    return parent::buildForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-
-    $workflow_label = $this->workflow->getState($this->stateId)->label();
-    $this->workflow
-      ->deleteState($this->stateId)
-      ->save();
-
-    drupal_set_message($this->t(
-      'State %label deleted.',
-      ['%label' => $workflow_label]
-    ));
-
-    $form_state->setRedirectUrl($this->getCancelUrl());
-  }
-
-}
diff --git a/core/modules/workflows/src/Form/WorkflowStateEditForm.php b/core/modules/workflows/src/Form/WorkflowStateEditForm.php
deleted file mode 100644
index 6ee33f3..0000000
--- a/core/modules/workflows/src/Form/WorkflowStateEditForm.php
+++ /dev/null
@@ -1,167 +0,0 @@
-<?php
-
-namespace Drupal\workflows\Form;
-
-use Drupal\Core\Entity\EntityForm;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Url;
-
-/**
- * Class WorkflowStateEditForm.
- */
-class WorkflowStateEditForm extends EntityForm {
-
-  /**
-   * The ID of the state that is being edited.
-   *
-   * @var string
-   */
-  protected $stateId;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state, $workflow_state = NULL) {
-    $this->stateId = $workflow_state;
-    return parent::buildForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function form(array $form, FormStateInterface $form_state) {
-    $form = parent::form($form, $form_state);
-
-    /* @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->getEntity();
-    $state = $workflow->getState($this->stateId);
-    $form['label'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Label'),
-      '#maxlength' => 255,
-      '#default_value' => $state->label(),
-      '#description' => $this->t('Label for the state.'),
-      '#required' => TRUE,
-    ];
-
-    $form['id'] = [
-      '#type' => 'machine_name',
-      '#default_value' => $this->stateId,
-      '#machine_name' => [
-        'exists' => [$this, 'exists'],
-      ],
-      '#disabled' => TRUE,
-    ];
-
-    // Add additional form fields from the workflow type plugin.
-    $form['type_settings'] = [
-      $workflow->get('type') => $workflow->getTypePlugin()->buildStateConfigurationForm($form_state, $workflow, $state),
-      '#tree' => TRUE,
-    ];
-
-    $header = [
-      'label' => $this->t('Transition'),
-      'state' => $this->t('To'),
-      'operations' => $this->t('Operations'),
-    ];
-    $form['transitions'] = [
-      '#type' => 'table',
-      '#header' => $header,
-      '#empty' => $this->t('There are no states yet.'),
-    ];
-    foreach ($state->getTransitions() as $transition) {
-      $links['edit'] = [
-        'title' => $this->t('Edit'),
-        'url' => Url::fromRoute('entity.workflow.edit_transition_form', [
-          'workflow' => $workflow->id(),
-          'workflow_transition' => $transition->id()
-        ]),
-      ];
-      $links['delete'] = [
-        'title' => t('Delete'),
-        'url' => Url::fromRoute('entity.workflow.delete_transition_form', [
-          'workflow' => $workflow->id(),
-          'workflow_transition' => $transition->id()
-        ]),
-      ];
-      $form['transitions'][$transition->id()] = [
-        'label' => [
-          '#markup' => $transition->label(),
-        ],
-        'state' => [
-          '#markup' => $transition->to()->label(),
-        ],
-        'operations' => [
-          '#type' => 'operations',
-          '#links' => $links,
-        ],
-      ];
-    }
-
-    return $form;
-  }
-
-  /**
-   * Copies top-level form values to entity properties
-   *
-   * This form can only change values for a state, which is part of workflow.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity the current form should operate upon.
-   * @param array $form
-   *   A nested array of form elements comprising the form.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   */
-  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
-    /** @var \Drupal\workflows\WorkflowInterface $entity */
-    $values = $form_state->getValues();
-    $entity->setStateLabel($values['id'], $values['label']);
-    if (isset($values['type_settings'])) {
-      $configuration = $entity->getTypePlugin()->getConfiguration();
-      $configuration['states'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()->getPluginId()];
-      $entity->set('type_settings', $configuration);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function save(array $form, FormStateInterface $form_state) {
-    /** @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->entity;
-    $workflow->save();
-    drupal_set_message($this->t('Saved %label state.', [
-      '%label' => $workflow->getState($this->stateId)->label(),
-    ]));
-    $form_state->setRedirectUrl($workflow->toUrl('edit-form'));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function actions(array $form, FormStateInterface $form_state) {
-    $actions['submit'] = [
-      '#type' => 'submit',
-      '#value' => $this->t('Save'),
-      '#submit' => ['::submitForm', '::save'],
-    ];
-
-    $actions['delete'] = [
-      '#type' => 'link',
-      '#title' => $this->t('Delete'),
-      '#access' => $this->entity->access('delete-state'),
-      '#attributes' => [
-        'class' => ['button', 'button--danger'],
-      ],
-      '#url' => Url::fromRoute('entity.workflow.delete_state_form', [
-        'workflow' => $this->entity->id(),
-        'workflow_state' => $this->stateId
-      ])
-    ];
-
-    return $actions;
-  }
-
-}
diff --git a/core/modules/workflows/src/Form/WorkflowTransitionAddForm.php b/core/modules/workflows/src/Form/WorkflowTransitionAddForm.php
deleted file mode 100644
index 1c557d9..0000000
--- a/core/modules/workflows/src/Form/WorkflowTransitionAddForm.php
+++ /dev/null
@@ -1,151 +0,0 @@
-<?php
-
-namespace Drupal\workflows\Form;
-
-use Drupal\Core\Entity\EntityForm;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\workflows\State;
-
-/**
- * Class WorkflowTransitionAddForm.
- */
-class WorkflowTransitionAddForm extends EntityForm {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function form(array $form, FormStateInterface $form_state) {
-    $form = parent::form($form, $form_state);
-
-    /* @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->getEntity();
-    $form['label'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Label'),
-      '#maxlength' => 255,
-      '#default_value' => '',
-      '#description' => $this->t('Label for the transition.'),
-      '#required' => TRUE,
-    ];
-
-    $form['id'] = [
-      '#type' => 'machine_name',
-      '#machine_name' => [
-        'exists' => [$this, 'exists'],
-      ],
-    ];
-
-    // @todo https://www.drupal.org/node/2830584 Add some ajax to ensure that
-    //   only valid transitions are selectable.
-    $states = array_map([State::class, 'labelCallback'], $workflow->getStates());
-    $form['from'] = [
-      '#type' => 'checkboxes',
-      '#title' => $this->t('From'),
-      '#required' => TRUE,
-      '#default_value' => [],
-      '#options' => $states,
-    ];
-    $form['to'] = [
-      '#type' => 'radios',
-      '#title' => $this->t('To'),
-      '#required' => TRUE,
-      '#default_value' => [],
-      '#options' => $states,
-    ];
-
-    // Add additional form fields from the workflow type plugin.
-    $form['type_settings'] = [
-      $workflow->get('type') => $workflow->getTypePlugin()->buildTransitionConfigurationForm($form_state, $workflow),
-      '#tree' => TRUE,
-    ];
-
-    return $form;
-  }
-
-  /**
-   * Determines if the workflow transition already exists.
-   *
-   * @param string $transition_id
-   *   The workflow transition ID.
-   *
-   * @return bool
-   *   TRUE if the workflow transition exists, FALSE otherwise.
-   */
-  public function exists($transition_id) {
-    /** @var \Drupal\workflows\WorkflowInterface $original_workflow */
-    $original_workflow = \Drupal::entityTypeManager()->getStorage('workflow')->loadUnchanged($this->getEntity()->id());
-    return $original_workflow->hasTransition($transition_id);
-  }
-
-  /**
-   * Copies top-level form values to entity properties
-   *
-   * This form can only change values for a state, which is part of workflow.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity the current form should operate upon.
-   * @param array $form
-   *   A nested array of form elements comprising the form.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   */
-  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
-    if (!$form_state->isValidationComplete()) {
-      // Only do something once form validation is complete.
-      return;
-    }
-    /** @var \Drupal\workflows\WorkflowInterface $entity */
-    $values = $form_state->getValues();
-    $entity->addTransition($values['id'], $values['label'], array_filter($values['from']), $values['to']);
-    if (isset($values['type_settings'])) {
-      $configuration = $entity->getTypePlugin()->getConfiguration();
-      $configuration['transitions'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()->getPluginId()];
-      $entity->set('type_settings', $configuration);
-    }
-  }
-
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state) {
-    /** @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->getEntity();
-    $values = $form_state->getValues();
-    foreach (array_filter($values['from']) as $from_state_id) {
-      if ($workflow->hasTransitionFromStateToState($from_state_id, $values['to'])) {
-        $form_state->setErrorByName('from][' . $from_state_id, $this->t('The transition from %from to %to already exists.', [
-          '%from' => $workflow->getState($from_state_id)->label(),
-          '%to' => $workflow->getState($values['to'])->label(),
-        ]));
-      }
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function save(array $form, FormStateInterface $form_state) {
-    /** @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->entity;
-    $workflow->save();
-    drupal_set_message($this->t('Created %label transition.', [
-      '%label' => $form_state->getValue('label'),
-    ]));
-    $form_state->setRedirectUrl($workflow->toUrl('edit-form'));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function actions(array $form, FormStateInterface $form_state) {
-    $actions['submit'] = [
-      '#type' => 'submit',
-      '#value' => $this->t('Save'),
-      '#submit' => ['::submitForm', '::save'],
-    ];
-    return $actions;
-  }
-
-}
diff --git a/core/modules/workflows/src/Form/WorkflowTransitionDeleteForm.php b/core/modules/workflows/src/Form/WorkflowTransitionDeleteForm.php
deleted file mode 100644
index abcb41e..0000000
--- a/core/modules/workflows/src/Form/WorkflowTransitionDeleteForm.php
+++ /dev/null
@@ -1,102 +0,0 @@
-<?php
-
-namespace Drupal\workflows\Form;
-
-use Drupal\workflows\WorkflowInterface;
-use Drupal\Core\Form\ConfirmFormBase;
-use Drupal\Core\Form\FormStateInterface;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
-
-/**
- * Builds the form to delete transitions from Workflow entities.
- */
-class WorkflowTransitionDeleteForm extends ConfirmFormBase {
-
-  /**
-   * The workflow entity the transition being deleted belongs to.
-   *
-   * @var \Drupal\workflows\WorkflowInterface
-   */
-  protected $workflow;
-
-  /**
-   * The workflow transition being deleted.
-   *
-   * @var \Drupal\workflows\TransitionInterface
-   */
-  protected $transition;
-
-  /**
-   * The transition being deleted.
-   *
-   * @var string
-   */
-  protected $transitionId;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'workflow_transition_delete_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getQuestion() {
-    return $this->t('Are you sure you want to delete %transition from %workflow?', ['%transition' => $this->transition->label(), '%workflow' => $this->workflow->label()]);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getCancelUrl() {
-    return $this->workflow->toUrl();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getConfirmText() {
-    return $this->t('Delete');
-  }
-
-  /**
-   * Form constructor.
-   *
-   * @param array $form
-   *   An associative array containing the structure of the form.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   * @param \Drupal\workflows\WorkflowInterface $workflow
-   *   The workflow entity being edited.
-   * @param string|null $workflow_transition
-   *   The workflow transition being deleted.
-   *
-   * @return array
-   *   The form structure.
-   */
-  public function buildForm(array $form, FormStateInterface $form_state, WorkflowInterface $workflow = NULL, $workflow_transition = NULL) {
-    try {
-      $this->transition = $workflow->getTransition($workflow_transition);
-    }
-    catch (\InvalidArgumentException $e) {
-      throw new NotFoundHttpException();
-    }
-    $this->workflow = $workflow;
-    return parent::buildForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    $this->workflow
-      ->deleteTransition($this->transition->id())
-      ->save();
-
-    drupal_set_message($this->t('%transition transition deleted.', ['%transition' => $this->transition->label()]));
-    $form_state->setRedirectUrl($this->getCancelUrl());
-  }
-
-}
diff --git a/core/modules/workflows/src/Form/WorkflowTransitionEditForm.php b/core/modules/workflows/src/Form/WorkflowTransitionEditForm.php
deleted file mode 100644
index b6f65a4..0000000
--- a/core/modules/workflows/src/Form/WorkflowTransitionEditForm.php
+++ /dev/null
@@ -1,171 +0,0 @@
-<?php
-
-namespace Drupal\workflows\Form;
-
-use Drupal\Core\Entity\EntityForm;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Url;
-use Drupal\workflows\State;
-
-/**
- * Class WorkflowTransitionEditForm.
- */
-class WorkflowTransitionEditForm extends EntityForm {
-
-  /**
-   * The ID of the transition that is being edited.
-   *
-   * @var string
-   */
-  protected $transitionId;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state, $workflow_transition = NULL) {
-    $this->transitionId = $workflow_transition;
-    return parent::buildForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function form(array $form, FormStateInterface $form_state) {
-    $form = parent::form($form, $form_state);
-
-    /* @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->getEntity();
-    $transition = $workflow->getTransition($this->transitionId);
-    $form['label'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Label'),
-      '#maxlength' => 255,
-      '#default_value' => $transition->label(),
-      '#description' => $this->t('Label for the transition.'),
-      '#required' => TRUE,
-    ];
-
-    $form['id'] = [
-      '#type' => 'value',
-      '#value' => $this->transitionId,
-    ];
-
-    // @todo https://www.drupal.org/node/2830584 Add some ajax to ensure that
-    //   only valid transitions are selectable.
-    $states = array_map([State::class, 'labelCallback'], $workflow->getStates());
-    $form['from'] = [
-      '#type' => 'checkboxes',
-      '#title' => $this->t('From'),
-      '#required' => TRUE,
-      '#default_value' => array_keys($transition->from()),
-      '#options' => $states,
-    ];
-    $form['to'] = [
-      '#type' => 'radios',
-      '#title' => $this->t('To'),
-      '#required' => TRUE,
-      '#default_value' => $transition->to()->id(),
-      '#options' => $states,
-      '#disabled' => TRUE,
-    ];
-
-    // Add additional form fields from the workflow type plugin.
-    $form['type_settings'] = [
-      $workflow->get('type') => $workflow->getTypePlugin()->buildTransitionConfigurationForm($form_state, $workflow, $transition),
-      '#tree' => TRUE,
-    ];
-
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state) {
-    /** @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->getEntity();
-    $values = $form_state->getValues();
-    foreach (array_filter($values['from']) as $from_state_id) {
-      if ($workflow->hasTransitionFromStateToState($from_state_id, $values['to'])) {
-        $transition = $workflow->getTransitionFromStateToState($from_state_id, $values['to']);
-        if ($transition->id() !== $values['id']) {
-          $form_state->setErrorByName('from][' . $from_state_id, $this->t('The transition from %from to %to already exists.', [
-            '%from' => $workflow->getState($from_state_id)->label(),
-            '%to' => $workflow->getState($values['to'])->label(),
-          ]));
-        }
-      }
-    }
-  }
-
-  /**
-   * Copies top-level form values to entity properties
-   *
-   * This form can only change values for a state, which is part of workflow.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity the current form should operate upon.
-   * @param array $form
-   *   A nested array of form elements comprising the form.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   */
-  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
-    if (!$form_state->isValidationComplete()) {
-      // Only do something once form validation is complete.
-      return;
-    }
-    /** @var \Drupal\workflows\WorkflowInterface $entity */
-    $values = $form_state->getValues();
-    $form_state->set('created_transition', FALSE);
-    $entity->setTransitionLabel($values['id'], $values['label']);
-    $entity->setTransitionFromStates($values['id'], array_filter($values['from']));
-    if (isset($values['type_settings'])) {
-      $configuration = $entity->getTypePlugin()->getConfiguration();
-      $configuration['transitions'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()->getPluginId()];
-      $entity->set('type_settings', $configuration);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function save(array $form, FormStateInterface $form_state) {
-    /** @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->entity;
-    $workflow->save();
-    drupal_set_message($this->t('Saved %label transition.', [
-      '%label' => $workflow->getTransition($this->transitionId)->label(),
-    ]));
-    $form_state->setRedirectUrl($workflow->toUrl('edit-form'));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function actions(array $form, FormStateInterface $form_state) {
-    $actions['submit'] = [
-      '#type' => 'submit',
-      '#value' => $this->t('Save'),
-      '#submit' => ['::submitForm', '::save'],
-    ];
-
-    $actions['delete'] = [
-      '#type' => 'link',
-      '#title' => $this->t('Delete'),
-      // Deleting a transition is editing a workflow.
-      '#access' => $this->entity->access('edit'),
-      '#attributes' => [
-        'class' => ['button', 'button--danger'],
-      ],
-      '#url' => Url::fromRoute('entity.workflow.delete_transition_form', [
-        'workflow' => $this->entity->id(),
-        'workflow_transition' => $this->transitionId
-      ])
-    ];
-
-    return $actions;
-  }
-
-}
diff --git a/core/modules/workflows/src/Plugin/WorkflowTypeBase.php b/core/modules/workflows/src/Plugin/WorkflowTypeBase.php
deleted file mode 100644
index 1b910e9..0000000
--- a/core/modules/workflows/src/Plugin/WorkflowTypeBase.php
+++ /dev/null
@@ -1,119 +0,0 @@
-<?php
-
-namespace Drupal\workflows\Plugin;
-
-use Drupal\Component\Plugin\PluginBase;
-use Drupal\Component\Utility\NestedArray;
-use Drupal\Core\Access\AccessResult;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Session\AccountInterface;
-use Drupal\workflows\StateInterface;
-use Drupal\workflows\TransitionInterface;
-use Drupal\workflows\WorkflowInterface;
-use Drupal\workflows\WorkflowTypeInterface;
-
-/**
- * A base class for Workflow type plugins.
- *
- * @see \Drupal\workflows\Annotation\WorkflowType
- *
- * @internal
- *   The workflow system is currently experimental and should only be leveraged
- *   by experimental modules and development releases of contributed modules.
- */
-abstract class WorkflowTypeBase extends PluginBase implements WorkflowTypeInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function label() {
-    $definition = $this->getPluginDefinition();
-    // The label can be an object.
-    // @see \Drupal\Core\StringTranslation\TranslatableMarkup
-    return $definition['label'];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function checkWorkflowAccess(WorkflowInterface $entity, $operation, AccountInterface $account) {
-    return AccessResult::neutral();
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public function decorateState(StateInterface $state) {
-    return $state;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public function deleteState($state_id) {
-    unset($this->configuration['states'][$state_id]);
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public function decorateTransition(TransitionInterface $transition) {
-    return $transition;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public function deleteTransition($transition_id) {
-    unset($this->configuration['transitions'][$transition_id]);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildStateConfigurationForm(FormStateInterface $form_state, WorkflowInterface $workflow, StateInterface $state = NULL) {
-    return [];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildTransitionConfigurationForm(FormStateInterface $form_state, WorkflowInterface $workflow, TransitionInterface $transition = NULL) {
-    return [];
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public function getConfiguration() {
-    return $this->configuration;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public function setConfiguration(array $configuration) {
-    $this->configuration = NestedArray::mergeDeep(
-      $this->defaultConfiguration(),
-      $configuration
-    );
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public function defaultConfiguration() {
-    return [
-      'states' => [],
-      'transitions' => [],
-    ];
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public function calculateDependencies() {
-    return [];
-  }
-
-}
diff --git a/core/modules/workflows/src/State.php b/core/modules/workflows/src/State.php
deleted file mode 100644
index f8b4fae..0000000
--- a/core/modules/workflows/src/State.php
+++ /dev/null
@@ -1,118 +0,0 @@
-<?php
-
-namespace Drupal\workflows;
-
-/**
- * A value object representing a workflow state.
- *
- * @internal
- *   The workflow system is currently experimental and should only be leveraged
- *   by experimental modules and development releases of contributed modules.
- */
-class State implements StateInterface {
-
-  /**
-   * The workflow the state is attached to.
-   *
-   * @var \Drupal\workflows\WorkflowInterface
-   */
-  protected $workflow;
-
-  /**
-   * The state's ID.
-   *
-   * @var string
-   */
-  protected $id;
-
-  /**
-   * The state's label.
-   *
-   * @var string
-   */
-  protected $label;
-
-  /**
-   * The state's weight.
-   *
-   * @var int
-   */
-  protected $weight;
-
-  /**
-   * State constructor.
-   *
-   * @param \Drupal\workflows\WorkflowInterface $workflow
-   *   The workflow the state is attached to.
-   * @param string $id
-   *   The state's ID.
-   * @param string $label
-   *   The state's label.
-   * @param int $weight
-   *   The state's weight.
-   */
-  public function __construct(WorkflowInterface $workflow, $id, $label, $weight = 0) {
-    $this->workflow = $workflow;
-    $this->id = $id;
-    $this->label = $label;
-    $this->weight = $weight;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function id() {
-    return $this->id;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function label() {
-    return $this->label;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function weight() {
-    return $this->weight;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function canTransitionTo($to_state_id) {
-    return $this->workflow->hasTransitionFromStateToState($this->id, $to_state_id);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getTransitionTo($to_state_id) {
-    if (!$this->canTransitionTo($to_state_id)) {
-      throw new \InvalidArgumentException("Can not transition to '$to_state_id' state");
-    }
-    return $this->workflow->getTransitionFromStateToState($this->id(), $to_state_id);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getTransitions() {
-    return $this->workflow->getTransitionsForState($this->id);
-  }
-
-  /**
-   * Helper method to convert a list of states to labels
-   *
-   * @param \Drupal\workflows\StateInterface $state
-   *
-   * @return string
-   *   The label of the state.
-   */
-  public static function labelCallback(StateInterface $state) {
-    return $state->label();
-  }
-
-}
diff --git a/core/modules/workflows/src/StateInterface.php b/core/modules/workflows/src/StateInterface.php
deleted file mode 100644
index 6aa16e9..0000000
--- a/core/modules/workflows/src/StateInterface.php
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-
-namespace Drupal\workflows;
-
-/**
- * An interface for state value objects.
- *
- * @see \Drupal\workflows\WorkflowTypeInterface::decorateState()
- *
- * @internal
- *   The workflow system is currently experimental and should only be leveraged
- *   by experimental modules and development releases of contributed modules.
- */
-interface StateInterface {
-
-  /**
-   * Gets the state's ID.
-   *
-   * @return string
-   *   The state's ID.
-   */
-  public function id();
-
-  /**
-   * Gets the state's label.
-   *
-   * @return string
-   *   The state's label.
-   */
-  public function label();
-
-  /**
-   * Gets the state's weight.
-   *
-   * @return int
-   *   The state's weight.
-   */
-  public function weight();
-
-  /**
-   * Determines if the state can transition to the provided state ID.
-   *
-   * @param $to_state_id
-   *   The state to transition to.
-   *
-   * @return bool
-   *   TRUE if the state can transition to the provided state ID. FALSE, if not.
-   */
-  public function canTransitionTo($to_state_id);
-
-  /**
-   * Gets the Transition object for the provided state ID.
-   *
-   * @param $to_state_id
-   *   The state to transition to.
-   *
-   * @return \Drupal\workflows\TransitionInterface
-   *   The Transition object for the provided state ID.
-   *
-   * @throws \InvalidArgumentException()
-   *   Exception thrown when the provided state ID can not be transitioned to.
-   */
-  public function getTransitionTo($to_state_id);
-
-  /**
-   * Gets all the possible transition objects for the state.
-   *
-   * @return \Drupal\workflows\TransitionInterface[]
-   *   All the possible transition objects for the state.
-   */
-  public function getTransitions();
-
-}
diff --git a/core/modules/workflows/src/Transition.php b/core/modules/workflows/src/Transition.php
deleted file mode 100644
index d8c21b6..0000000
--- a/core/modules/workflows/src/Transition.php
+++ /dev/null
@@ -1,116 +0,0 @@
-<?php
-
-namespace Drupal\workflows;
-
-/**
- * A transition value object that describes the transition between states.
- *
- * @internal
- *   The workflow system is currently experimental and should only be leveraged
- *   by experimental modules and development releases of contributed modules.
- */
-class Transition implements TransitionInterface {
-
-  /**
-   * The workflow that this transition is attached to.
-   *
-   * @var \Drupal\workflows\WorkflowInterface
-   */
-  protected $workflow;
-
-  /**
-   * The transition's ID.
-   *
-   * @var string
-   */
-  protected $id;
-
-  /**
-   * The transition's label.
-   *
-   * @var string
-   */
-  protected $label;
-
-  /**
-   * The transition's from state IDs.
-   *
-   * @var string[]
-   */
-  protected $fromStateIds;
-
-  /**
-   * The transition's to state ID.
-   *
-   * @var string
-   */
-  protected $toStateId;
-
-  /**
-   * The transition's weight.
-   *
-   * @var int
-   */
-  protected $weight;
-
-  /**
-   * Transition constructor.
-   *
-   * @param \Drupal\workflows\WorkflowInterface $workflow
-   *   The workflow the state is attached to.
-   * @param string $id
-   *   The transition's ID.
-   * @param string $label
-   *   The transition's label.
-   * @param array $from_state_ids
-   *   A list of from state IDs.
-   * @param string $to_state_id
-   *   The to state ID.
-   * @param int $weight
-   *   (optional) The transition's weight. Defaults to 0.
-   */
-  public function __construct(WorkflowInterface $workflow, $id, $label, array $from_state_ids, $to_state_id, $weight = 0) {
-    $this->workflow = $workflow;
-    $this->id = $id;
-    $this->label = $label;
-    $this->fromStateIds = $from_state_ids;
-    $this->toStateId = $to_state_id;
-    $this->weight = $weight;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function id() {
-    return $this->id;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function label() {
-    return $this->label;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function from() {
-    return $this->workflow->getStates($this->fromStateIds);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function to() {
-    return $this->workflow->getState($this->toStateId);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function weight() {
-    return $this->weight;
-  }
-
-}
diff --git a/core/modules/workflows/src/TransitionInterface.php b/core/modules/workflows/src/TransitionInterface.php
deleted file mode 100644
index c178b22..0000000
--- a/core/modules/workflows/src/TransitionInterface.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-
-namespace Drupal\workflows;
-
-/**
- * A transition value object that describes the transition between two states.
- *
- * @internal
- *   The workflow system is currently experimental and should only be leveraged
- *   by experimental modules and development releases of contributed modules.
- */
-interface TransitionInterface {
-
-  /**
-   * Gets the transition's ID.
-   *
-   * @return string
-   *   The transition's ID.
-   */
-  public function id();
-
-  /**
-   * Gets the transition's label.
-   *
-   * @return string
-   *   The transition's label.
-   */
-  public function label();
-
-  /**
-   * Gets the transition's from states.
-   *
-   * @return \Drupal\workflows\StateInterface[]
-   *   The transition's from states.
-   */
-  public function from();
-
-  /**
-   * Gets the transition's to state.
-   *
-   * @return \Drupal\workflows\StateInterface
-   *   The transition's to state.
-   */
-  public function to();
-
-  /**
-   * Gets the transition's weight.
-   *
-   * @return string
-   *   The transition's weight.
-   */
-  public function weight();
-
-}
diff --git a/core/modules/workflows/src/WorkflowAccessControlHandler.php b/core/modules/workflows/src/WorkflowAccessControlHandler.php
deleted file mode 100644
index dbeedf5..0000000
--- a/core/modules/workflows/src/WorkflowAccessControlHandler.php
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-
-namespace Drupal\workflows;
-
-use Drupal\Component\Plugin\PluginManagerInterface;
-use Drupal\Core\Entity\EntityAccessControlHandler;
-use Drupal\Core\Entity\EntityHandlerInterface;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Session\AccountInterface;
-use Drupal\Core\Access\AccessResult;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Access controller for the Moderation State entity.
- *
- * @see \Drupal\workflows\Entity\Workflow.
- *
- * @internal
- *   The workflow system is currently experimental and should only be leveraged
- *   by experimental modules and development releases of contributed modules.
- */
-class WorkflowAccessControlHandler extends EntityAccessControlHandler implements EntityHandlerInterface {
-
-  /**
-   * The workflow type plugin manager.
-   *
-   * @var \Drupal\Component\Plugin\PluginManagerInterface
-   */
-  protected $workflowTypeManager;
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
-    return new static(
-      $entity_type,
-      $container->get('plugin.manager.workflows.type')
-    );
-  }
-
-  /**
-   * Constructs the workflow access control handler instance.
-   *
-   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
-   *   The entity type definition.
-   * @param \Drupal\Component\Plugin\PluginManagerInterface $workflow_type_manager
-   *   The workflow type plugin manager.
-   */
-  public function __construct(EntityTypeInterface $entity_type, PluginManagerInterface $workflow_type_manager) {
-    parent::__construct($entity_type);
-    $this->workflowTypeManager = $workflow_type_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
-    if ($operation === 'delete-state') {
-      // Deleting a state is editing a workflow, but also we should forbid
-      // access if there is only one state.
-      /** @var \Drupal\workflows\Entity\Workflow $entity */
-      $admin_access = AccessResult::allowedIf(count($entity->getStates()) > 1)->andIf(parent::checkAccess($entity, 'edit', $account))->addCacheableDependency($entity);
-    }
-    else {
-      $admin_access = parent::checkAccess($entity, $operation, $account);
-    }
-    /** @var \Drupal\workflows\WorkflowInterface $entity */
-    return $entity->getTypePlugin()->checkWorkflowAccess($entity, $operation, $account)->orIf($admin_access);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
-    $workflow_types_count = count($this->workflowTypeManager->getDefinitions());
-    $admin_access = parent::checkCreateAccess($account, $context, $entity_bundle);
-    // Allow access if there is at least one workflow type. Since workflow types
-    // are provided by modules this is cacheable until extensions change.
-    return $admin_access->andIf(AccessResult::allowedIf($workflow_types_count > 0))->addCacheTags(['config:core.extension']);
-  }
-
-}
diff --git a/core/modules/workflows/src/WorkflowInterface.php b/core/modules/workflows/src/WorkflowInterface.php
deleted file mode 100644
index 0efedea..0000000
--- a/core/modules/workflows/src/WorkflowInterface.php
+++ /dev/null
@@ -1,289 +0,0 @@
-<?php
-
-namespace Drupal\workflows;
-
-use Drupal\Core\Config\Entity\ConfigEntityInterface;
-
-/**
- * Provides an interface for defining workflow entities.
- *
- * @internal
- *   The workflow system is currently experimental and should only be leveraged
- *   by experimental modules and development releases of contributed modules.
- */
-interface WorkflowInterface extends ConfigEntityInterface {
-
-  /**
-   * Adds a state to the workflow.
-   *
-   * @param string $state_id
-   *   The state's ID.
-   * @param string $label
-   *   The state's label.
-   *
-   * @return \Drupal\workflows\WorkflowInterface
-   *   The workflow entity.
-   */
-  public function addState($state_id, $label);
-
-  /**
-   * Determines if the workflow has a state with the provided ID.
-   *
-   * @param string $state_id
-   *   The state's ID.
-   *
-   * @return bool
-   *   TRUE if the workflow has a state with the provided ID, FALSE if not.
-   */
-  public function hasState($state_id);
-
-  /**
-   * Gets state objects for the provided state IDs.
-   *
-   * @param string[] $state_ids
-   *   A list of state IDs to get. If NULL then all states will be returned.
-   *
-   * @return \Drupal\workflows\StateInterface[]
-   *   An array of workflow states.
-   *
-   * @throws \InvalidArgumentException
-   *   Thrown if $state_ids contains a state ID that does not exist.
-   */
-  public function getStates($state_ids = NULL);
-
-  /**
-   * Gets a workflow state.
-   *
-   * @param string $state_id
-   *   The state's ID.
-   *
-   * @return \Drupal\workflows\StateInterface
-   *   The workflow state.
-   *
-   * @throws \InvalidArgumentException
-   *   Thrown if $state_id does not exist.
-   */
-  public function getState($state_id);
-
-  /**
-   * Sets a state's label.
-   *
-   * @param string $state_id
-   *   The state ID to set the label for.
-   * @param string $label
-   *   The state's label.
-   *
-   * @return \Drupal\workflows\WorkflowInterface
-   *   The workflow entity.
-   */
-  public function setStateLabel($state_id, $label);
-
-  /**
-   * Sets a state's weight value.
-   *
-   * @param string $state_id
-   *   The state ID to set the weight for.
-   * @param int $weight
-   *   The state's weight.
-   *
-   * @return \Drupal\workflows\WorkflowInterface
-   *   The workflow entity.
-   */
-  public function setStateWeight($state_id, $weight);
-
-  /**
-   * Deletes a state from the workflow.
-   *
-   * @param string $state_id
-   *   The state ID to delete.
-   *
-   * @return \Drupal\workflows\WorkflowInterface
-   *   The workflow entity.
-   *
-   * @throws \InvalidArgumentException
-   *   Thrown if $state_id does not exist.
-   */
-  public function deleteState($state_id);
-
-  /**
-   * Gets the initial state for the workflow.
-   *
-   * @return \Drupal\workflows\StateInterface
-   *   The initial state.
-   */
-  public function getInitialState();
-
-  /**
-   * Adds a transition to the workflow.
-   *
-   * @param string $id
-   *   The transition ID.
-   * @param string $label.
-   *   The transition's label.
-   * @param array $from_state_ids
-   *   The state IDs to transition from.
-   * @param string $to_state_id
-   *   The state ID to transition to.
-   *
-   * @return \Drupal\workflows\WorkflowInterface
-   *   The workflow entity.
-   *
-   * @throws \InvalidArgumentException
-   *   Thrown if either state does not exist.
-   */
-  public function addTransition($id, $label, array $from_state_ids, $to_state_id);
-
-  /**
-   * Gets a transition object for the provided transition ID.
-   *
-   * @param string $transition_id
-   *   A transition ID.
-   *
-   * @return \Drupal\workflows\TransitionInterface
-   *   The transition.
-   *
-   * @throws \InvalidArgumentException
-   *   Thrown if $transition_id does not exist.
-   */
-  public function getTransition($transition_id);
-
-  /**
-   * Determines if a transition exists.
-   *
-   * @param string $transition_id
-   *   The transition ID.
-   *
-   * @return bool
-   *   TRUE if the transition exists, FALSE if not.
-   */
-  public function hasTransition($transition_id);
-
-  /**
-   * Gets transition objects for the provided transition IDs.
-   *
-   * @param string[] $transition_ids
-   *   A list of transition IDs to get. If NULL then all transitions will be
-   *   returned.
-   *
-   * @return \Drupal\workflows\TransitionInterface[]
-   *   An array of transition objects.
-   *
-   * @throws \InvalidArgumentException
-   *   Thrown if $transition_ids contains a transition ID that does not exist.
-   */
-  public function getTransitions(array $transition_ids = NULL);
-
-  /**
-   * Gets the transactions IDs for a state for the provided direction.
-   *
-   * @param $state_id
-   *   The state to get transitions for.
-   * @param string $direction
-   *   (optional) The direction of the transition. Defaults to 'from'. Possible
-   *   values are: 'from' and 'to'.
-   *
-   * @return array
-   *   The transactions IDs for a state for the provided direction.
-   */
-  public function getTransitionsForState($state_id, $direction = 'from');
-
-  /**
-   * Gets a transition from state to state.
-   *
-   * @param string $from_state_id
-   *   The state ID to transition from.
-   * @param string $to_state_id
-   *   The state ID to transition to.
-   *
-   * @return \Drupal\workflows\TransitionInterface
-   *   The transitions.
-   *
-   * @throws \InvalidArgumentException
-   *   Thrown if the transition does not exist.
-   */
-  public function getTransitionFromStateToState($from_state_id, $to_state_id);
-
-  /**
-   * Determines if a transition from state to state exists.
-   *
-   * @param string $from_state_id
-   *   The state ID to transition from.
-   * @param string $to_state_id
-   *   The state ID to transition to.
-   *
-   * @return bool
-   *   TRUE if the transition exists, FALSE if not.
-   */
-  public function hasTransitionFromStateToState($from_state_id, $to_state_id);
-
-  /**
-   * Sets a transition's label.
-   *
-   * @param string $transition_id
-   *   The transition ID.
-   * @param string $label
-   *   The transition's label.
-   *
-   * @return \Drupal\workflows\WorkflowInterface
-   *   The workflow entity.
-   *
-   * @throws \InvalidArgumentException
-   *   Thrown if the transition does not exist.
-   */
-  public function setTransitionLabel($transition_id, $label);
-
-  /**
-   * Sets a transition's weight.
-   *
-   * @param string $transition_id
-   *   The transition ID.
-   * @param int $weight
-   *   The transition's weight.
-   *
-   * @return \Drupal\workflows\WorkflowInterface
-   *   The workflow entity.
-   *
-   * @throws \InvalidArgumentException
-   *   Thrown if the transition does not exist.
-   */
-  public function setTransitionWeight($transition_id, $weight);
-
-  /**
-   * Sets a transition's from states.
-   *
-   * @param string $transition_id
-   *   The transition ID.
-   * @param array $from_state_ids
-   *   The state IDs to transition from.
-   *
-   * @return \Drupal\workflows\WorkflowInterface
-   *   The workflow entity.
-   *
-   * @throws \InvalidArgumentException
-   *   Thrown if the transition does not exist or the states do not exist.
-   */
-  public function setTransitionFromStates($transition_id, array   $from_state_ids);
-
-  /**
-   * Deletes a transition.
-   *
-   * @param string $transition_id
-   *   The transition ID.
-   *
-   * @return \Drupal\workflows\WorkflowInterface
-   *   The workflow entity.
-   *
-   * @throws \InvalidArgumentException
-   *   Thrown if the transition does not exist.
-   */
-  public function deleteTransition($transition_id);
-
-  /**
-   * Gets the workflow type plugin.
-   *
-   * @return \Drupal\workflows\WorkflowTypeInterface
-   *   The workflow type plugin.
-   */
-  public function getTypePlugin();
-
-}
diff --git a/core/modules/workflows/src/WorkflowListBuilder.php b/core/modules/workflows/src/WorkflowListBuilder.php
deleted file mode 100644
index 3e94103..0000000
--- a/core/modules/workflows/src/WorkflowListBuilder.php
+++ /dev/null
@@ -1,101 +0,0 @@
-<?php
-
-namespace Drupal\workflows;
-
-use Drupal\Component\Plugin\PluginManagerInterface;
-use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Entity\EntityTypeInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Provides a listing of Workflow entities.
- */
-class WorkflowListBuilder extends ConfigEntityListBuilder {
-
-  /**
-   * The workflow type plugin manager.
-   *
-   * @var \Drupal\Component\Plugin\PluginManagerInterface
-   */
-  protected $workflowTypeManager;
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
-    return new static(
-      $entity_type,
-      $container->get('entity_type.manager')->getStorage($entity_type->id()),
-      $container->get('plugin.manager.workflows.type')
-    );
-  }
-
-  /**
-   * Constructs a new WorkflowListBuilder object.
-   *
-   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
-   *   The entity type definition.
-   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
-   *   The entity storage class.
-   * @param \Drupal\Component\Plugin\PluginManagerInterface $workflow_type_manager
-   *   The workflow type plugin manager.
-   */
-  public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, PluginManagerInterface $workflow_type_manager) {
-    parent::__construct($entity_type, $storage);
-    $this->workflowTypeManager = $workflow_type_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'workflow_admin_overview_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildHeader() {
-    $header['label'] = $this->t('Workflow');
-    $header['type'] = $this->t('Type');
-    $header['states'] = $this->t('States');
-
-    return $header + parent::buildHeader();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildRow(EntityInterface $entity) {
-    /** @var \Drupal\workflows\WorkflowInterface $entity */
-    $row['label'] = $entity->label();
-
-    $row['type']['data'] = [
-      '#markup' => $entity->getTypePlugin()->label()
-    ];
-
-    $items = array_map([State::class, 'labelCallback'], $entity->getStates());
-    $row['states']['data'] = [
-      '#theme' => 'item_list',
-      '#context' => ['list_style' => 'comma-list'],
-      '#items' => $items,
-    ];
-
-    return $row + parent::buildRow($entity);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function render() {
-    $build = parent::render();
-    $workflow_types_count = count($this->workflowTypeManager->getDefinitions());
-    if ($workflow_types_count === 0) {
-      $build['table']['#empty'] = $this->t('There are no workflow types available. In order to create workflows you need to install a module that provides a workflow type. For example, the Content Moderation module provides a workflow type that enables workflows for content entities.');
-    }
-    return $build;
-  }
-
-}
diff --git a/core/modules/workflows/src/WorkflowTypeInterface.php b/core/modules/workflows/src/WorkflowTypeInterface.php
deleted file mode 100644
index 17fddec..0000000
--- a/core/modules/workflows/src/WorkflowTypeInterface.php
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-
-namespace Drupal\workflows;
-
-use Drupal\Component\Plugin\ConfigurablePluginInterface;
-use Drupal\Component\Plugin\DerivativeInspectionInterface;
-use Drupal\Component\Plugin\PluginInspectionInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Session\AccountInterface;
-
-/**
- * An interface for Workflow type plugins.
- *
- * @internal
- *   The workflow system is currently experimental and should only be leveraged
- *   by experimental modules and development releases of contributed modules.
- */
-interface WorkflowTypeInterface extends PluginInspectionInterface, DerivativeInspectionInterface, ConfigurablePluginInterface {
-
-  /**
-   * Gets the label for the workflow type.
-   *
-   * @return string
-   *   The workflow type label.
-   */
-  public function label();
-
-  /**
-   * Performs access checks.
-   *
-   * @param \Drupal\workflows\WorkflowInterface $entity
-   *   The workflow entity for which to check access.
-   * @param string $operation
-   *   The entity operation. Usually one of 'view', 'view label', 'update' or
-   *   'delete'.
-   * @param \Drupal\Core\Session\AccountInterface $account
-   *   The user for which to check access.
-   *
-   * @return \Drupal\Core\Access\AccessResultInterface
-   *   The access result.
-   */
-  public function checkWorkflowAccess(WorkflowInterface $entity, $operation, AccountInterface $account);
-
-  /**
-   * Decorates states so the WorkflowType can add additional information.
-   *
-   * @param \Drupal\workflows\StateInterface $state
-   *   The state object to decorate.
-   *
-   * @return \Drupal\workflows\StateInterface $state
-   *   The decorated state object.
-   */
-  public function decorateState(StateInterface $state);
-
-  /**
-   * React to the removal of a state from a workflow.
-   *
-   * @param string $state_id
-   *   The state ID of the state that is being removed.
-   */
-  public function deleteState($state_id);
-
-  /**
-   * Decorates transitions so the WorkflowType can add additional information.
-   * @param \Drupal\workflows\TransitionInterface $transition
-   *   The transition object to decorate.
-   *
-   * @return \Drupal\workflows\TransitionInterface $transition
-   *   The decorated transition object.
-   */
-  public function decorateTransition(TransitionInterface $transition);
-
-  /**
-   * React to the removal of a transition from a workflow.
-   *
-   * @param string $transition_id
-   *   The transition ID of the transition that is being removed.
-   */
-  public function deleteTransition($transition_id);
-
-  /**
-   * Builds a form to be added to the Workflow state edit form.
-   *
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The form state.
-   * @param \Drupal\workflows\WorkflowInterface $workflow
-   *   The workflow the state is attached to.
-   * @param \Drupal\workflows\StateInterface|null $state
-   *   The workflow state being edited. If NULL, a new state is being added.
-   *
-   * @return array
-   *   Form elements to add to a workflow state form for customisations to the
-   *   workflow.
-   *
-   * @see \Drupal\workflows\Form\WorkflowStateAddForm::form()
-   * @see \Drupal\workflows\Form\WorkflowStateEditForm::form()
-   */
-  public function buildStateConfigurationForm(FormStateInterface $form_state, WorkflowInterface $workflow, StateInterface $state = NULL);
-
-  /**
-   * Builds a form to be added to the Workflow transition edit form.
-   *
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The form state.
-   * @param \Drupal\workflows\WorkflowInterface $workflow
-   *   The workflow the state is attached to.
-   * @param \Drupal\workflows\TransitionInterface|null $transition
-   *   The workflow transition being edited. If NULL, a new transition is being
-   *   added.
-   *
-   * @return array
-   *   Form elements to add to a workflow transition form for customisations to
-   *   the workflow.
-   *
-   * @see \Drupal\workflows\Form\WorkflowTransitionAddForm::form()
-   * @see \Drupal\workflows\Form\WorkflowTransitionEditForm::form()
-   */
-  public function buildTransitionConfigurationForm(FormStateInterface $form_state, WorkflowInterface $workflow, TransitionInterface $transition = NULL);
-
-}
diff --git a/core/modules/workflows/src/WorkflowTypeManager.php b/core/modules/workflows/src/WorkflowTypeManager.php
deleted file mode 100644
index b2eafa8..0000000
--- a/core/modules/workflows/src/WorkflowTypeManager.php
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-namespace Drupal\workflows;
-
-use Drupal\Core\Cache\CacheBackendInterface;
-use Drupal\Core\Extension\ModuleHandlerInterface;
-use Drupal\Core\Plugin\DefaultPluginManager;
-use Drupal\workflows\Annotation\WorkflowType;
-
-/**
- * Provides a Workflow type plugin manager.
- *
- * @see \Drupal\workflows\Annotation\WorkflowType
- * @see \Drupal\workflows\WorkflowTypeInterface
- * @see plugin_api
- *
- * @internal
- *   The workflow system is currently experimental and should only be leveraged
- *   by experimental modules and development releases of contributed modules.
- */
-class WorkflowTypeManager extends DefaultPluginManager {
-
-  /**
-   * Constructs a new class instance.
-   *
-   * @param \Traversable $namespaces
-   *   An object that implements \Traversable which contains the root paths
-   *   keyed by the corresponding namespace to look for plugin implementations.
-   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
-   *   Cache backend instance to use.
-   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
-   *   The module handler to invoke the alter hook with.
-   */
-  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
-    parent::__construct('Plugin/WorkflowType', $namespaces, $module_handler, WorkflowTypeInterface::class, WorkflowType::class);
-    $this->alterInfo('workflow_type_info');
-    $this->setCacheBackend($cache_backend, 'workflow_type_info');
-  }
-
-}
diff --git a/core/modules/workflows/tests/modules/workflow_type_test/config/schema/workflow_type_test.schema.yml b/core/modules/workflows/tests/modules/workflow_type_test/config/schema/workflow_type_test.schema.yml
deleted file mode 100644
index 4f12fdd..0000000
--- a/core/modules/workflows/tests/modules/workflow_type_test/config/schema/workflow_type_test.schema.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-workflow.type_settings.workflow_type_test:
-  type: mapping
-  label: 'Workflow test type settings'
-  mapping:
-    states:
-      type: sequence
-      sequence:
-        type: ignore
-
-workflow.type_settings.workflow_type_complex_test:
-  type: mapping
-  label: 'Workflow complex test type settings'
-  mapping:
-    states:
-      type: sequence
-      label: 'Additional state configuration'
-      sequence:
-        type: mapping
-        label: 'States'
-        mapping:
-          extra:
-            type: string
-            label: 'Extra information'
-    transitions:
-      type: sequence
-      label: 'Additional transition configuration'
-      sequence:
-        type: mapping
-        label: 'Transitions'
-        mapping:
-          extra:
-            type: string
-            label: 'Extra information'
diff --git a/core/modules/workflows/tests/modules/workflow_type_test/src/DecoratedState.php b/core/modules/workflows/tests/modules/workflow_type_test/src/DecoratedState.php
deleted file mode 100644
index 793e899..0000000
--- a/core/modules/workflows/tests/modules/workflow_type_test/src/DecoratedState.php
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php
-
-namespace Drupal\workflow_type_test;
-
-use Drupal\workflows\StateInterface;
-
-/**
- * A value object representing a workflow state.
- */
-class DecoratedState implements StateInterface {
-
-  /**
-   * The vanilla state object from the Workflow module.
-   *
-   * @var \Drupal\workflows\StateInterface
-   */
-  protected $state;
-
-  /**
-   * Extra information added to state.
-   *
-   * @var string
-   */
-  protected $extra;
-
-  /**
-   * DecoratedState constructor.
-   *
-   * @param \Drupal\workflows\StateInterface $state
-   *   The vanilla state object from the Workflow module.
-   * @param string $extra
-   *   (optional) Extra information stored on the state. Defaults to ''.
-   */
-  public function __construct(StateInterface $state, $extra = '') {
-    $this->state = $state;
-    $this->extra = $extra;
-  }
-
-  /**
-   * Gets the extra information stored on the state.
-   *
-   * @return string
-   */
-  public function getExtra() {
-    return $this->extra;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function id() {
-    return $this->state->id();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function label() {
-    return $this->state->label();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function weight() {
-    return $this->state->weight();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function canTransitionTo($to_state_id) {
-    return $this->state->canTransitionTo($to_state_id);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getTransitionTo($to_state_id) {
-    return $this->state->getTransitionTo($to_state_id);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getTransitions() {
-    return $this->state->getTransitions();
-  }
-
-}
diff --git a/core/modules/workflows/tests/modules/workflow_type_test/src/DecoratedTransition.php b/core/modules/workflows/tests/modules/workflow_type_test/src/DecoratedTransition.php
deleted file mode 100644
index d7690d5..0000000
--- a/core/modules/workflows/tests/modules/workflow_type_test/src/DecoratedTransition.php
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-
-namespace Drupal\workflow_type_test;
-
-use Drupal\workflows\TransitionInterface;
-
-/**
- * A value object representing a workflow transition.
- */
-class DecoratedTransition implements TransitionInterface {
-
-  /**
-   * The vanilla transition object from the Workflow module.
-   *
-   * @var \Drupal\workflows\TransitionInterface
-   */
-  protected $transition;
-
-  /**
-   * Extra information added to transition.
-   *
-   * @var string
-   */
-  protected $extra;
-
-  /**
-   * DecoratedTransition constructor.
-   *
-   * @param \Drupal\workflows\TransitionInterface $transition
-   *   The vanilla transition object from the Workflow module.
-   * @param string $extra
-   *   (optional) Extra information stored on the transition. Defaults to ''.
-   */
-  public function __construct(TransitionInterface $transition, $extra = '') {
-    $this->transition = $transition;
-    $this->extra = $extra;
-  }
-
-  /**
-   * Gets the extra information stored on the transition.
-   *
-   * @return string
-   */
-  public function getExtra() {
-    return $this->extra;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function id() {
-    return $this->transition->id();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function label() {
-    return $this->transition->label();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function from() {
-    return $this->transition->from();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function to() {
-    return $this->transition->to();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function weight() {
-    return $this->transition->weight();
-  }
-
-}
diff --git a/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/ComplexTestType.php b/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/ComplexTestType.php
deleted file mode 100644
index 8440c1c..0000000
--- a/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/ComplexTestType.php
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-
-namespace Drupal\workflow_type_test\Plugin\WorkflowType;
-
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\workflows\Plugin\WorkflowTypeBase;
-use Drupal\workflows\StateInterface;
-use Drupal\workflows\TransitionInterface;
-use Drupal\workflows\WorkflowInterface;
-use Drupal\workflow_type_test\DecoratedState;
-use Drupal\workflow_type_test\DecoratedTransition;
-
-/**
- * Test workflow type.
- *
- * @WorkflowType(
- *   id = "workflow_type_complex_test",
- *   label = @Translation("Workflow Type Complex Test"),
- * )
- */
-class ComplexTestType extends WorkflowTypeBase {
-
-  use StringTranslationTrait;
-
-  /**
-   * {@inheritDoc}
-   */
-  public function decorateState(StateInterface $state) {
-    if (isset($this->configuration['states'][$state->id()])) {
-      $state = new DecoratedState($state, $this->configuration['states'][$state->id()]['extra']);
-    }
-    else {
-      $state = new DecoratedState($state);
-    }
-    return $state;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public function decorateTransition(TransitionInterface $transition) {
-    if (isset($this->configuration['transitions'][$transition->id()])) {
-      $transition = new DecoratedTransition($transition, $this->configuration['transitions'][$transition->id()]['extra']);
-    }
-    else {
-      $transition = new DecoratedTransition($transition);
-    }
-    return $transition;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildStateConfigurationForm(FormStateInterface $form_state, WorkflowInterface $workflow, StateInterface $state = NULL) {
-    /** @var \Drupal\workflow_type_test\DecoratedState $state */
-    $form = [];
-    $form['extra'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Extra'),
-      '#description' => $this->t('Extra information added to state'),
-      '#default_value' => isset($state) ? $state->getExtra() : FALSE,
-    ];
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildTransitionConfigurationForm(FormStateInterface $form_state, WorkflowInterface $workflow, TransitionInterface $transition = NULL) {
-    /** @var \Drupal\workflow_type_test\DecoratedTransition $transition */
-    $form = [];
-    $form['extra'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Extra'),
-      '#description' => $this->t('Extra information added to transition'),
-      '#default_value' => isset($transition) ? $transition->getExtra() : FALSE,
-    ];
-    return $form;
-  }
-
-}
diff --git a/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/TestType.php b/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/TestType.php
deleted file mode 100644
index 78ebe03..0000000
--- a/core/modules/workflows/tests/modules/workflow_type_test/src/Plugin/WorkflowType/TestType.php
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-
-namespace Drupal\workflow_type_test\Plugin\WorkflowType;
-
-use Drupal\workflows\Plugin\WorkflowTypeBase;
-
-/**
- * Test workflow type.
- *
- * @WorkflowType(
- *   id = "workflow_type_test",
- *   label = @Translation("Workflow Type Test"),
- * )
- */
-class TestType extends WorkflowTypeBase {
-}
diff --git a/core/modules/workflows/tests/modules/workflow_type_test/workflow_type_test.info.yml b/core/modules/workflows/tests/modules/workflow_type_test/workflow_type_test.info.yml
deleted file mode 100644
index 80a8c89..0000000
--- a/core/modules/workflows/tests/modules/workflow_type_test/workflow_type_test.info.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-name: 'Workflow Type Test'
-type: module
-description: 'Provides a workflow type plugin for testing.'
-package: Testing
-version: VERSION
-core: 8.x
-dependencies:
-  - workflows
diff --git a/core/modules/workflows/tests/src/Functional/WorkflowUiNoTypeTest.php b/core/modules/workflows/tests/src/Functional/WorkflowUiNoTypeTest.php
deleted file mode 100644
index c0c7de6..0000000
--- a/core/modules/workflows/tests/src/Functional/WorkflowUiNoTypeTest.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-
-namespace Drupal\Tests\workflows\Functional;
-
-use Drupal\Tests\BrowserTestBase;
-
-/**
- * Tests workflow UI when there are no types.
- *
- * @group workflows
- */
-class WorkflowUiNoTypeTest extends BrowserTestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = ['workflows', 'block'];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-    // We're testing local actions.
-    $this->drupalPlaceBlock('local_actions_block');
-  }
-
-  /**
-   * Tests the creation of a workflow through the UI.
-   */
-  public function testWorkflowUiWithNoType() {
-    $this->drupalLogin($this->createUser(['access administration pages', 'administer workflows']));
-    $this->drupalGet('admin/config/workflow/workflows/add');
-    // There are no workflow types so this should be a 403.
-    $this->assertSession()->statusCodeEquals(403);
-
-    $this->drupalGet('admin/config/workflow/workflows');
-    $this->assertSession()->pageTextContains('There are no workflow types available. In order to create workflows you need to install a module that provides a workflow type. For example, the Content Moderation module provides a workflow type that enables workflows for content entities.');
-    $this->assertSession()->pageTextNotContains('Add workflow');
-
-    $this->container->get('module_installer')->install(['workflow_type_test']);
-    // The render cache needs to be cleared because although the cache tags are
-    // correctly set the render cache does not pick it up.
-    \Drupal::cache('render')->deleteAll();
-
-    $this->drupalGet('admin/config/workflow/workflows');
-    $this->assertSession()->pageTextNotContains('There are no workflow types available. In order to create workflows you need to install a module that provides a workflow type. For example, the Content Moderation module provides a workflow type that enables workflows for content entities.');
-    $this->assertSession()->linkExists('Add workflow');
-    $this->assertSession()->pageTextContains('There is no Workflow yet.');
-  }
-
-}
diff --git a/core/modules/workflows/tests/src/Functional/WorkflowUiTest.php b/core/modules/workflows/tests/src/Functional/WorkflowUiTest.php
deleted file mode 100644
index ed5eb6f..0000000
--- a/core/modules/workflows/tests/src/Functional/WorkflowUiTest.php
+++ /dev/null
@@ -1,262 +0,0 @@
-<?php
-
-namespace Drupal\Tests\workflows\Functional;
-
-use Drupal\Core\Url;
-use Drupal\Tests\BrowserTestBase;
-use Drupal\workflows\Entity\Workflow;
-
-/**
- * Tests workflow creation UI.
- *
- * @group workflows
- */
-class WorkflowUiTest extends BrowserTestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = ['workflows', 'workflow_type_test', 'block'];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-    // We're testing local actions.
-    $this->drupalPlaceBlock('local_actions_block');
-  }
-
-  /**
-   * Tests route access/permissions.
-   */
-  public function testAccess() {
-    // Create a minimal workflow for testing.
-    $workflow = Workflow::create(['id' => 'test', 'type' => 'workflow_type_test']);
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
-      ->save();
-
-    $paths = [
-      'admin/config/workflow/workflows',
-      'admin/config/workflow/workflows/add',
-      'admin/config/workflow/workflows/manage/test',
-      'admin/config/workflow/workflows/manage/test/delete',
-      'admin/config/workflow/workflows/manage/test/add_state',
-      'admin/config/workflow/workflows/manage/test/state/published',
-      'admin/config/workflow/workflows/manage/test/state/published/delete',
-      'admin/config/workflow/workflows/manage/test/add_transition',
-      'admin/config/workflow/workflows/manage/test/transition/publish',
-      'admin/config/workflow/workflows/manage/test/transition/publish/delete',
-    ];
-
-    foreach ($paths as $path) {
-      $this->drupalGet($path);
-      // No access.
-      $this->assertSession()->statusCodeEquals(403);
-    }
-    $this->drupalLogin($this->createUser(['administer workflows']));
-    foreach ($paths as $path) {
-      $this->drupalGet($path);
-      // User has access.
-      $this->assertSession()->statusCodeEquals(200);
-    }
-
-    // Delete one of the states and ensure the other test cannot be deleted.
-    $this->drupalGet('admin/config/workflow/workflows/manage/test/state/published/delete');
-    $this->submitForm([], 'Delete');
-    $this->drupalGet('admin/config/workflow/workflows/manage/test/state/published/delete');
-    $this->assertSession()->statusCodeEquals(403);
-  }
-
-  /**
-   * Tests the creation of a workflow through the UI.
-   */
-  public function testWorkflowCreation() {
-    $workflow_storage = $this->container->get('entity_type.manager')->getStorage('workflow');
-    /** @var \Drupal\workflows\WorkflowInterface $workflow */
-    $this->drupalLogin($this->createUser(['access administration pages', 'administer workflows']));
-    $this->drupalGet('admin/config/workflow');
-    $this->assertSession()->linkByHrefExists('admin/config/workflow/workflows');
-    $this->clickLink('Workflows');
-    $this->assertSession()->pageTextContains('There is no Workflow yet.');
-    $this->clickLink('Add workflow');
-    $this->submitForm(['label' => 'Test', 'id' => 'test', 'workflow_type' => 'workflow_type_test'], 'Save');
-    $this->assertSession()->pageTextContains('Created the Test Workflow.');
-    $this->assertSession()->addressEquals('admin/config/workflow/workflows/manage/test/add_state');
-    $this->drupalGet('/admin/config/workflow/workflows/manage/test');
-    $this->assertSession()->pageTextContains('This workflow has no states and will be disabled until there is at least one, add a new state.');
-    $this->assertSession()->pageTextContains('There are no states yet.');
-    $this->clickLink('Add a new state');
-    $this->submitForm(['label' => 'Published', 'id' => 'published'], 'Save');
-    $this->assertSession()->pageTextContains('Created Published state.');
-    $workflow = $workflow_storage->loadUnchanged('test');
-    $this->assertFalse($workflow->getState('published')->canTransitionTo('published'), 'No default transition from published to published exists.');
-
-    $this->clickLink('Add a new state');
-    // Don't create a draft to draft transition by default.
-    $this->submitForm(['label' => 'Draft', 'id' => 'draft'], 'Save');
-    $this->assertSession()->pageTextContains('Created Draft state.');
-    $workflow = $workflow_storage->loadUnchanged('test');
-    $this->assertFalse($workflow->getState('draft')->canTransitionTo('draft'), 'Can not transition from draft to draft');
-
-    $this->clickLink('Add a new transition');
-    $this->submitForm(['id' => 'publish', 'label' => 'Publish', 'from[draft]' => 'draft', 'to' => 'published'], 'Save');
-    $this->assertSession()->pageTextContains('Created Publish transition.');
-    $workflow = $workflow_storage->loadUnchanged('test');
-    $this->assertTrue($workflow->getState('draft')->canTransitionTo('published'), 'Can transition from draft to published');
-
-    $this->clickLink('Add a new transition');
-    $this->submitForm(['id' => 'create_new_draft', 'label' => 'Create new draft', 'from[draft]' => 'draft', 'to' => 'draft'], 'Save');
-    $this->assertSession()->pageTextContains('Created Create new draft transition.');
-    $workflow = $workflow_storage->loadUnchanged('test');
-    $this->assertTrue($workflow->getState('draft')->canTransitionTo('draft'), 'Can transition from draft to draft');
-
-    // The fist state to edit on the page should be published.
-    $this->clickLink('Edit');
-    $this->assertSession()->fieldValueEquals('label', 'Published');
-    // Change the label.
-    $this->submitForm(['label' => 'Live'], 'Save');
-    $this->assertSession()->pageTextContains('Saved Live state.');
-
-    // Allow published to draft.
-    $this->clickLink('Edit', 3);
-    $this->submitForm(['from[published]' => 'published'], 'Save');
-    $this->assertSession()->pageTextContains('Saved Create new draft transition.');
-    $workflow = $workflow_storage->loadUnchanged('test');
-    $this->assertTrue($workflow->getState('published')->canTransitionTo('draft'), 'Can transition from published to draft');
-
-    // Try creating a duplicate transition.
-    $this->clickLink('Add a new transition');
-    $this->submitForm(['id' => 'create_new_draft', 'label' => 'Create new draft', 'from[published]' => 'published', 'to' => 'draft'], 'Save');
-    $this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
-    // Try creating a transition which duplicates the states of another.
-    $this->submitForm(['id' => 'create_new_draft2', 'label' => 'Create new draft again', 'from[published]' => 'published', 'to' => 'draft'], 'Save');
-    $this->assertSession()->pageTextContains('The transition from Live to Draft already exists.');
-
-    // Create a new transition.
-    $this->submitForm(['id' => 'save_and_publish', 'label' => 'Save and publish', 'from[published]' => 'published', 'to' => 'published'], 'Save');
-    $this->assertSession()->pageTextContains('Created Save and publish transition.');
-    // Edit the new transition and try to add an existing transition.
-    $this->clickLink('Edit', 4);
-    $this->submitForm(['from[draft]' => 'draft'], 'Save');
-    $this->assertSession()->pageTextContains('The transition from Draft to Live already exists.');
-
-    // Delete the transition.
-    $workflow = $workflow_storage->loadUnchanged('test');
-    $this->assertTrue($workflow->hasTransitionFromStateToState('published', 'published'), 'Can transition from published to published');
-    $this->clickLink('Delete');
-    $this->assertSession()->pageTextContains('Are you sure you want to delete Save and publish from Test?');
-    $this->submitForm([], 'Delete');
-    $workflow = $workflow_storage->loadUnchanged('test');
-    $this->assertFalse($workflow->hasTransitionFromStateToState('published', 'published'), 'Cannot transition from published to published');
-
-    // Try creating a duplicate state.
-    $this->drupalGet('admin/config/workflow/workflows/manage/test');
-    $this->clickLink('Add a new state');
-    $this->submitForm(['label' => 'Draft', 'id' => 'draft'], 'Save');
-    $this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
-
-    // Ensure that weight changes the state ordering.
-    $workflow = $workflow_storage->loadUnchanged('test');
-    $this->assertEquals('published', $workflow->getInitialState()->id());
-    $this->drupalGet('admin/config/workflow/workflows/manage/test');
-    $this->submitForm(['states[draft][weight]' => '-1'], 'Save');
-    $workflow = $workflow_storage->loadUnchanged('test');
-    $this->assertEquals('draft', $workflow->getInitialState()->id());
-
-    // This will take us to the list of workflows, so we need to edit the
-    // workflow again.
-    $this->clickLink('Edit');
-
-    // Ensure that weight changes the transition ordering.
-    $this->assertEquals(['publish', 'create_new_draft'], array_keys($workflow->getTransitions()));
-    $this->drupalGet('admin/config/workflow/workflows/manage/test');
-    $this->submitForm(['transitions[create_new_draft][weight]' => '-1'], 'Save');
-    $workflow = $workflow_storage->loadUnchanged('test');
-    $this->assertEquals(['create_new_draft', 'publish'], array_keys($workflow->getTransitions()));
-
-    // This will take us to the list of workflows, so we need to edit the
-    // workflow again.
-    $this->clickLink('Edit');
-
-    // Ensure that a delete link for the published state exists before deleting
-    // the draft state.
-    $published_delete_link = Url::fromRoute('entity.workflow.delete_state_form', [
-      'workflow' => $workflow->id(),
-      'workflow_state' => 'published'
-    ])->toString();
-    $this->assertSession()->linkByHrefExists($published_delete_link);
-
-    // Delete the Draft state.
-    $this->clickLink('Delete');
-    $this->assertSession()->pageTextContains('Are you sure you want to delete Draft from Test?');
-    $this->submitForm([], 'Delete');
-    $this->assertSession()->pageTextContains('State Draft deleted.');
-    $workflow = $workflow_storage->loadUnchanged('test');
-    $this->assertFalse($workflow->hasState('draft'), 'Draft state deleted');
-    $this->assertTrue($workflow->hasState('published'), 'Workflow still has published state');
-
-    // The last state cannot be deleted so the only delete link on the page will
-    // be for the workflow.
-    $this->assertSession()->linkByHrefNotExists($published_delete_link);
-    $this->clickLink('Delete');
-    $this->assertSession()->pageTextContains('Are you sure you want to delete Test?');
-    $this->submitForm([], 'Delete');
-    $this->assertSession()->pageTextContains('Workflow Test deleted.');
-    $this->assertSession()->pageTextContains('There is no Workflow yet.');
-    $this->assertNull($workflow_storage->loadUnchanged('test'), 'The test workflow has been deleted');
-  }
-
-  /**
-   * Tests that workflow types can add form fields to states and transitions.
-   */
-  public function testWorkflowDecoration() {
-    // Create a minimal workflow for testing.
-    $workflow = Workflow::create(['id' => 'test', 'type' => 'workflow_type_complex_test']);
-    $workflow
-      ->addState('published', 'Published')
-      ->addTransition('publish', 'Publish', ['published'], 'published')
-      ->save();
-
-    $this->assertEquals('', $workflow->getState('published')->getExtra());
-    $this->assertEquals('', $workflow->getTransition('publish')->getExtra());
-
-    $this->drupalLogin($this->createUser(['administer workflows']));
-
-    // Add additional state information when editing.
-    $this->drupalGet('admin/config/workflow/workflows/manage/test/state/published');
-    $this->assertSession()->pageTextContains('Extra information added to state');
-    $this->submitForm(['type_settings[workflow_type_complex_test][extra]' => 'Extra state information'], 'Save');
-
-    // Add additional transition information when editing.
-    $this->drupalGet('admin/config/workflow/workflows/manage/test/transition/publish');
-    $this->assertSession()->pageTextContains('Extra information added to transition');
-    $this->submitForm(['type_settings[workflow_type_complex_test][extra]' => 'Extra transition information'], 'Save');
-
-    $workflow_storage = $this->container->get('entity_type.manager')->getStorage('workflow');
-    /** @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $workflow_storage->loadUnchanged('test');
-    $this->assertEquals('Extra state information', $workflow->getState('published')->getExtra());
-    $this->assertEquals('Extra transition information', $workflow->getTransition('publish')->getExtra());
-
-    // Add additional state information when adding.
-    $this->drupalGet('admin/config/workflow/workflows/manage/test/add_state');
-    $this->assertSession()->pageTextContains('Extra information added to state');
-    $this->submitForm(['label' => 'Draft', 'id' => 'draft', 'type_settings[workflow_type_complex_test][extra]' => 'Extra state information on add'], 'Save');
-
-    // Add additional transition information when adding.
-    $this->drupalGet('admin/config/workflow/workflows/manage/test/add_transition');
-    $this->assertSession()->pageTextContains('Extra information added to transition');
-    $this->submitForm(['id' => 'draft_published', 'label' => 'Publish', 'from[draft]' => 'draft', 'to' => 'published', 'type_settings[workflow_type_complex_test][extra]' => 'Extra transition information on add'], 'Save');
-
-    $workflow = $workflow_storage->loadUnchanged('test');
-    $this->assertEquals('Extra state information on add', $workflow->getState('draft')->getExtra());
-    $this->assertEquals('Extra transition information on add', $workflow->getTransition('draft_published')->getExtra());
-  }
-
-}
diff --git a/core/modules/workflows/tests/src/Kernel/ComplexWorkflowTypeTest.php b/core/modules/workflows/tests/src/Kernel/ComplexWorkflowTypeTest.php
deleted file mode 100644
index 2ff1c2a..0000000
--- a/core/modules/workflows/tests/src/Kernel/ComplexWorkflowTypeTest.php
+++ /dev/null
@@ -1,55 +0,0 @@
-<?php
-
-namespace Drupal\Tests\workflows\Kernel;
-
-use Drupal\KernelTests\KernelTestBase;
-use Drupal\workflows\Entity\Workflow;
-use Drupal\workflow_type_test\DecoratedState;
-use Drupal\workflow_type_test\DecoratedTransition;
-
-/**
- * Workflow entity tests that require modules or storage.
- *
- * @coversDefaultClass \Drupal\workflows\Entity\Workflow
- *
- * @group workflows
- */
-class ComplexWorkflowTypeTest extends KernelTestBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public static $modules = ['workflows', 'workflow_type_test'];
-
-  /**
-   * Tests a workflow type that decorates transitions and states.
-   *
-   * @covers ::getState
-   * @covers ::getTransition
-   */
-  public function testComplexType() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'workflow_type_complex_test'], 'workflow');
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft');
-    $this->assertInstanceOf(DecoratedState::class, $workflow->getState('draft'));
-    $this->assertInstanceOf(DecoratedTransition::class, $workflow->getTransition('create_new_draft'));
-  }
-
-  /**
-   * @covers ::loadMultipleByType
-   */
-  public function testLoadMultipleByType() {
-    $workflow1 = new Workflow(['id' => 'test1', 'type' => 'workflow_type_complex_test'], 'workflow');
-    $workflow1->save();
-    $workflow2 = new Workflow(['id' => 'test2', 'type' => 'workflow_type_complex_test'], 'workflow');
-    $workflow2->save();
-    $workflow3 = new Workflow(['id' => 'test3', 'type' => 'workflow_type_test'], 'workflow');
-    $workflow3->save();
-
-    $this->assertEquals(['test1', 'test2'], array_keys(Workflow::loadMultipleByType('workflow_type_complex_test')));
-    $this->assertEquals(['test3'], array_keys(Workflow::loadMultipleByType('workflow_type_test')));
-    $this->assertEquals([], Workflow::loadMultipleByType('a_type_that_does_not_exist'));
-  }
-
-}
diff --git a/core/modules/workflows/tests/src/Unit/StateTest.php b/core/modules/workflows/tests/src/Unit/StateTest.php
deleted file mode 100644
index 82feca3..0000000
--- a/core/modules/workflows/tests/src/Unit/StateTest.php
+++ /dev/null
@@ -1,131 +0,0 @@
-<?php
-
-namespace Drupal\Tests\workflows\Unit;
-
-use Drupal\Core\DependencyInjection\ContainerBuilder;
-use Drupal\Tests\UnitTestCase;
-use Drupal\workflows\Entity\Workflow;
-use Drupal\workflows\State;
-use Drupal\workflows\WorkflowInterface;
-use Drupal\workflows\WorkflowTypeInterface;
-use Drupal\workflows\WorkflowTypeManager;
-use Prophecy\Argument;
-
-/**
- * @coversDefaultClass \Drupal\workflows\State
- *
- * @group workflows
- */
-class StateTest extends UnitTestCase {
-
-  /**
-   * Sets up the Workflow Type manager so that workflow entities can be used.
-   */
-  protected function setUp() {
-    parent::setUp();
-    // Create a container so that the plugin manager and workflow type can be
-    // mocked.
-    $container = new ContainerBuilder();
-    $workflow_type = $this->prophesize(WorkflowTypeInterface::class);
-    $workflow_type->decorateState(Argument::any())->willReturnArgument(0);
-    $workflow_type->decorateTransition(Argument::any())->willReturnArgument(0);
-    $workflow_type->deleteState(Argument::any())->willReturn(NULL);
-    $workflow_type->deleteTransition(Argument::any())->willReturn(NULL);
-    $workflow_manager = $this->prophesize(WorkflowTypeManager::class);
-    $workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal());
-    $container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
-    \Drupal::setContainer($container);
-  }
-
-  /**
-   * @covers ::__construct
-   * @covers ::id
-   * @covers ::label
-   * @covers ::weight
-   */
-  public function testGetters() {
-    $state = new State(
-      $this->prophesize(WorkflowInterface::class)->reveal(),
-      'draft',
-      'Draft',
-      3
-    );
-    $this->assertEquals('draft', $state->id());
-    $this->assertEquals('Draft', $state->label());
-    $this->assertEquals(3, $state->weight());
-  }
-
-  /**
-   * @covers ::canTransitionTo
-   */
-  public function testCanTransitionTo() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addTransition('publish', 'Publish', ['draft'], 'published');
-    $state = $workflow->getState('draft');
-    $this->assertTrue($state->canTransitionTo('published'));
-    $this->assertFalse($state->canTransitionTo('some_other_state'));
-
-    $workflow->deleteTransition('publish');
-    $this->assertFalse($state->canTransitionTo('published'));
-  }
-
-  /**
-   * @covers ::getTransitionTo
-   */
-  public function testGetTransitionTo() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addTransition('publish', 'Publish', ['draft'], 'published');
-    $state = $workflow->getState('draft');
-    $transition = $state->getTransitionTo('published');
-    $this->assertEquals('Publish', $transition->label());
-  }
-
-  /**
-   * @covers ::getTransitionTo
-   */
-  public function testGetTransitionToException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "Can not transition to 'published' state");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->addState('draft', 'Draft');
-    $state = $workflow->getState('draft');
-    $state->getTransitionTo('published');
-  }
-
-  /**
-   * @covers ::getTransitions
-   */
-  public function testGetTransitions() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addState('archived', 'Archived')
-      ->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
-      ->addTransition('publish', 'Publish', ['draft'], 'published')
-      ->addTransition('archive', 'Archive', ['published'], 'archived');
-    $state = $workflow->getState('draft');
-    $transitions = $state->getTransitions();
-    $this->assertCount(2, $transitions);
-    $this->assertEquals('Create new draft', $transitions['create_new_draft']->label());
-    $this->assertEquals('Publish', $transitions['publish']->label());
-  }
-
-  /**
-   * @covers ::labelCallback
-   */
-  public function testLabelCallback() {
-    $workflow = $this->prophesize(WorkflowInterface::class)->reveal();
-    $states = [
-      new State($workflow, 'draft', 'Draft'),
-      new State($workflow, 'published', 'Published'),
-    ];
-    $this->assertEquals(['Draft', 'Published'], array_map([State::class, 'labelCallback'], $states));
-  }
-
-}
diff --git a/core/modules/workflows/tests/src/Unit/TransitionTest.php b/core/modules/workflows/tests/src/Unit/TransitionTest.php
deleted file mode 100644
index 3202e8a..0000000
--- a/core/modules/workflows/tests/src/Unit/TransitionTest.php
+++ /dev/null
@@ -1,71 +0,0 @@
-<?php
-
-namespace Drupal\Tests\workflows\Unit;
-
-use Drupal\Core\DependencyInjection\ContainerBuilder;
-use Drupal\Tests\UnitTestCase;
-use Drupal\workflows\Entity\Workflow;
-use Drupal\workflows\Transition;
-use Drupal\workflows\WorkflowInterface;
-use Drupal\workflows\WorkflowTypeInterface;
-use Drupal\workflows\WorkflowTypeManager;
-use Prophecy\Argument;
-
-/**
- * @coversDefaultClass \Drupal\workflows\Transition
- *
- * @group workflows
- */
-class TransitionTest extends UnitTestCase {
-
-  /**
-   * Sets up the Workflow Type manager so that workflow entities can be used.
-   */
-  protected function setUp() {
-    parent::setUp();
-    // Create a container so that the plugin manager and workflow type can be
-    // mocked.
-    $container = new ContainerBuilder();
-    $workflow_type = $this->prophesize(WorkflowTypeInterface::class);
-    $workflow_type->decorateState(Argument::any())->willReturnArgument(0);
-    $workflow_type->decorateTransition(Argument::any())->willReturnArgument(0);
-    $workflow_manager = $this->prophesize(WorkflowTypeManager::class);
-    $workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal());
-    $container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
-    \Drupal::setContainer($container);
-  }
-
-  /**
-   * @covers ::__construct
-   * @covers ::id
-   * @covers ::label
-   */
-  public function testGetters() {
-    $state = new Transition(
-      $this->prophesize(WorkflowInterface::class)->reveal(),
-      'draft_published',
-      'Publish',
-      ['draft'],
-      'published'
-    );
-    $this->assertEquals('draft_published', $state->id());
-    $this->assertEquals('Publish', $state->label());
-  }
-
-  /**
-   * @covers ::from
-   * @covers ::to
-   */
-  public function testFromAndTo() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addTransition('publish', 'Publish', ['draft'], 'published');
-    $state = $workflow->getState('draft');
-    $transition = $state->getTransitionTo('published');
-    $this->assertEquals($state, $transition->from()['draft']);
-    $this->assertEquals($workflow->getState('published'), $transition->to());
-  }
-
-}
diff --git a/core/modules/workflows/tests/src/Unit/WorkflowTest.php b/core/modules/workflows/tests/src/Unit/WorkflowTest.php
deleted file mode 100644
index 49e8671..0000000
--- a/core/modules/workflows/tests/src/Unit/WorkflowTest.php
+++ /dev/null
@@ -1,654 +0,0 @@
-<?php
-
-namespace Drupal\Tests\workflows\Unit;
-
-use Drupal\Core\DependencyInjection\ContainerBuilder;
-use Drupal\Tests\UnitTestCase;
-use Drupal\workflows\Entity\Workflow;
-use Drupal\workflows\State;
-use Drupal\workflows\Transition;
-use Drupal\workflows\WorkflowTypeInterface;
-use Drupal\workflows\WorkflowTypeManager;
-use Prophecy\Argument;
-
-/**
- * @coversDefaultClass \Drupal\workflows\Entity\Workflow
- *
- * @group workflows
- */
-class WorkflowTest extends UnitTestCase {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-    // Create a container so that the plugin manager and workflow type can be
-    // mocked.
-    $container = new ContainerBuilder();
-    $workflow_type = $this->prophesize(WorkflowTypeInterface::class);
-    $workflow_type->decorateState(Argument::any())->willReturnArgument(0);
-    $workflow_type->decorateTransition(Argument::any())->willReturnArgument(0);
-    $workflow_manager = $this->prophesize(WorkflowTypeManager::class);
-    $workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal());
-    $container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
-    \Drupal::setContainer($container);
-  }
-
-  /**
-   * @covers ::addState
-   * @covers ::hasState
-   */
-  public function testAddAndHasState() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $this->assertFalse($workflow->hasState('draft'));
-
-    // By default states are ordered in the order added.
-    $workflow->addState('draft', 'Draft');
-    $this->assertTrue($workflow->hasState('draft'));
-    $this->assertFalse($workflow->hasState('published'));
-    $this->assertEquals(0, $workflow->getState('draft')->weight());
-    // Adding a state does not set up a transition to itself.
-    $this->assertFalse($workflow->hasTransitionFromStateToState('draft', 'draft'));
-
-    // New states are added with a new weight 1 more than the current highest
-    // weight.
-    $workflow->addState('published', 'Published');
-    $this->assertEquals(1, $workflow->getState('published')->weight());
-  }
-
-  /**
-   * @covers ::addState
-   */
-  public function testAddStateException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' already exists in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->addState('draft', 'Draft');
-    $workflow->addState('draft', 'Draft');
-  }
-
-  /**
-   * @covers ::addState
-   */
-  public function testAddStateInvalidIdException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The state ID 'draft-draft' must contain only lowercase letters, numbers, and underscores");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->addState('draft-draft', 'Draft');
-  }
-
-  /**
-   * @covers ::getStates
-   */
-  public function testGetStates() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-
-    // Getting states works when there are none.
-    $this->assertArrayEquals([], array_keys($workflow->getStates()));
-    $this->assertArrayEquals([], array_keys($workflow->getStates([])));
-
-    // By default states are ordered in the order added.
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addState('archived', 'Archived');
-
-    // Ensure we're returning state objects.
-    $this->assertInstanceOf(State::class, $workflow->getStates()['draft']);
-
-    // Passing in no IDs returns all states.
-    $this->assertArrayEquals(['draft', 'published', 'archived'], array_keys($workflow->getStates()));
-
-    // The order of states is by weight.
-    $workflow->setStateWeight('published', -1);
-    $this->assertArrayEquals(['published', 'draft', 'archived'], array_keys($workflow->getStates()));
-
-    // The label is also used for sorting if weights are equal.
-    $workflow->setStateWeight('archived', 0);
-    $this->assertArrayEquals(['published', 'archived', 'draft'], array_keys($workflow->getStates()));
-
-    // You can limit the states returned by passing in states IDs.
-    $this->assertArrayEquals(['archived', 'draft'], array_keys($workflow->getStates(['draft', 'archived'])));
-
-    // An empty array does not load all states.
-    $this->assertArrayEquals([], array_keys($workflow->getStates([])));
-  }
-
-  /**
-   * @covers ::getStates
-   */
-  public function testGetStatesException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The state 'state_that_does_not_exist' does not exist in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->getStates(['state_that_does_not_exist']);
-  }
-
-  /**
-   * @covers ::getState
-   */
-  public function testGetState() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    // By default states are ordered in the order added.
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addState('archived', 'Archived')
-      ->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
-      ->addTransition('publish', 'Publish', ['draft'], 'published');
-
-    // Ensure we're returning state objects and they are set up correctly
-    $this->assertInstanceOf(State::class, $workflow->getState('draft'));
-    $this->assertEquals('archived', $workflow->getState('archived')->id());
-    $this->assertEquals('Archived', $workflow->getState('archived')->label());
-
-    $draft = $workflow->getState('draft');
-    $this->assertTrue($draft->canTransitionTo('draft'));
-    $this->assertTrue($draft->canTransitionTo('published'));
-    $this->assertFalse($draft->canTransitionTo('archived'));
-    $this->assertEquals('Publish', $draft->getTransitionTo('published')->label());
-    $this->assertEquals(0, $draft->weight());
-    $this->assertEquals(1, $workflow->getState('published')->weight());
-    $this->assertEquals(2, $workflow->getState('archived')->weight());
-  }
-
-  /**
-   * @covers ::getState
-   */
-  public function testGetStateException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The state 'state_that_does_not_exist' does not exist in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->getState('state_that_does_not_exist');
-  }
-
-  /**
-   * @covers ::setStateLabel
-   */
-  public function testSetStateLabel() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->addState('draft', 'Draft');
-    $this->assertEquals('Draft', $workflow->getState('draft')->label());
-    $workflow->setStateLabel('draft', 'Unpublished');
-    $this->assertEquals('Unpublished', $workflow->getState('draft')->label());
-  }
-
-  /**
-   * @covers ::setStateLabel
-   */
-  public function testSetStateLabelException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->setStateLabel('draft', 'Draft');
-  }
-
-  /**
-   * @covers ::setStateWeight
-   */
-  public function testSetStateWeight() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->addState('draft', 'Draft');
-    $this->assertEquals(0, $workflow->getState('draft')->weight());
-    $workflow->setStateWeight('draft', -10);
-    $this->assertEquals(-10, $workflow->getState('draft')->weight());
-  }
-
-  /**
-   * @covers ::setStateWeight
-   */
-  public function testSetStateWeightException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->setStateWeight('draft', 10);
-  }
-
-  /**
-   * @covers ::deleteState
-   */
-  public function testDeleteState() {
-    // Create a container so that the plugin manager and workflow type can be
-    // mocked and test that
-    // \Drupal\workflows\WorkflowTypeInterface::deleteState() is called
-    // correctly.
-    $container = new ContainerBuilder();
-    $workflow_type = $this->prophesize(WorkflowTypeInterface::class);
-    $workflow_type->decorateState(Argument::any())->willReturnArgument(0);
-    $workflow_type->decorateTransition(Argument::any())->willReturnArgument(0);
-    $workflow_type->deleteState('draft')->shouldBeCalled();
-    $workflow_type->deleteTransition('create_new_draft')->shouldBeCalled();
-    $workflow_manager = $this->prophesize(WorkflowTypeManager::class);
-    $workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal());
-    $container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
-    \Drupal::setContainer($container);
-
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
-      ->addTransition('create_new_draft', 'Create new draft', ['draft', 'published'], 'draft');
-    $this->assertCount(2, $workflow->getStates());
-    $this->assertCount(2, $workflow->getState('published')->getTransitions());
-    $workflow->deleteState('draft');
-    $this->assertFalse($workflow->hasState('draft'));
-    $this->assertCount(1, $workflow->getStates());
-    $this->assertCount(1, $workflow->getState('published')->getTransitions());
-  }
-
-  /**
-   * @covers ::deleteState
-   */
-  public function testDeleteStateException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->deleteState('draft');
-  }
-
-  /**
-   * @covers ::deleteState
-   */
-  public function testDeleteOnlyStateException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' can not be deleted from workflow 'test' as it is the only state");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->addState('draft', 'Draft');
-    $workflow->deleteState('draft');
-  }
-
-  /**
-   * @covers ::getInitialState
-   */
-  public function testGetInitialState() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-
-    // By default states are ordered in the order added.
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addState('archived', 'Archived');
-
-    $this->assertEquals('draft', $workflow->getInitialState()->id());
-
-    // Make published the first state.
-    $workflow->setStateWeight('published', -1);
-    $this->assertEquals('published', $workflow->getInitialState()->id());
-  }
-
-  /**
-   * @covers ::addTransition
-   * @covers ::hasTransition
-   */
-  public function testAddTransition() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-
-    // By default states are ordered in the order added.
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published');
-
-    $this->assertFalse($workflow->getState('draft')->canTransitionTo('published'));
-    $workflow->addTransition('publish', 'Publish', ['draft'], 'published');
-    $this->assertTrue($workflow->getState('draft')->canTransitionTo('published'));
-    $this->assertEquals(0, $workflow->getTransition('publish')->weight());
-    $this->assertTrue($workflow->hasTransition('publish'));
-    $this->assertFalse($workflow->hasTransition('draft'));
-
-    $workflow->addTransition('save_publish', 'Save', ['published'], 'published');
-    $this->assertEquals(1, $workflow->getTransition('save_publish')->weight());
-  }
-
-  /**
-   * @covers ::addTransition
-   */
-  public function testAddTransitionDuplicateException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The transition 'publish' already exists in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->addState('published', 'Published');
-    $workflow->addTransition('publish', 'Publish', ['published'], 'published');
-    $workflow->addTransition('publish', 'Publish', ['published'], 'published');
-  }
-
-  /**
-   * @covers ::addTransition
-   */
-  public function testAddTransitionInvalidIdException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The transition ID 'publish-publish' must contain only lowercase letters, numbers, and underscores");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->addState('published', 'Published');
-    $workflow->addTransition('publish-publish', 'Publish', ['published'], 'published');
-  }
-
-  /**
-   * @covers ::addTransition
-   */
-  public function testAddTransitionMissingFromException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->addState('published', 'Published');
-    $workflow->addTransition('publish', 'Publish', ['draft'], 'published');
-  }
-
-  /**
-   * @covers ::addTransition
-   */
-  public function testAddTransitionDuplicateTransitionStatesException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The 'publish' transition already allows 'draft' to 'published' transitions in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published');
-    $workflow->addTransition('publish', 'Publish', ['draft', 'published'], 'published');
-    $workflow->addTransition('draft_to_published', 'Publish a draft', ['draft'], 'published');
-  }
-
-  /**
-   * @covers ::addTransition
-   */
-  public function testAddTransitionConsistentAfterFromCatch() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->addState('published', 'Published');
-    try {
-      $workflow->addTransition('publish', 'Publish', ['draft'], 'published');
-    }
-    catch (\InvalidArgumentException $e) {
-    }
-    // Ensure that the workflow is not left in an inconsistent state after an
-    // exception is thrown from Workflow::setTransitionFromStates() whilst
-    // calling Workflow::addTransition().
-    $this->assertFalse($workflow->hasTransition('publish'));
-  }
-
-  /**
-   * @covers ::addTransition
-   */
-  public function testAddTransitionMissingToException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The state 'published' does not exist in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->addState('draft', 'Draft');
-    $workflow->addTransition('publish', 'Publish', ['draft'], 'published');
-  }
-
-  /**
-   * @covers ::getTransitions
-   * @covers ::setTransitionWeight
-   */
-  public function testGetTransitions() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-
-    // Getting transitions works when there are none.
-    $this->assertArrayEquals([], array_keys($workflow->getTransitions()));
-    $this->assertArrayEquals([], array_keys($workflow->getTransitions([])));
-
-    // By default states are ordered in the order added.
-    $workflow
-      ->addState('a', 'A')
-      ->addState('b', 'B')
-      ->addTransition('a_a', 'A to A', ['a'], 'a')
-      ->addTransition('a_b', 'A to B', ['a'], 'b');
-
-    // Ensure we're returning transition objects.
-    $this->assertInstanceOf(Transition::class, $workflow->getTransitions()['a_a']);
-
-    // Passing in no IDs returns all transitions.
-    $this->assertArrayEquals(['a_a', 'a_b'], array_keys($workflow->getTransitions()));
-
-    // The order of states is by weight.
-    $workflow->setTransitionWeight('a_b', -1);
-    $this->assertArrayEquals(['a_b', 'a_a'], array_keys($workflow->getTransitions()));
-
-    // If all weights are equal it will fallback to labels.
-    $workflow->setTransitionWeight('a_b', 0);
-    $this->assertArrayEquals(['a_a', 'a_b'], array_keys($workflow->getTransitions()));
-    $workflow->setTransitionLabel('a_b', 'A B');
-    $this->assertArrayEquals(['a_b', 'a_a'], array_keys($workflow->getTransitions()));
-
-    // You can limit the states returned by passing in states IDs.
-    $this->assertArrayEquals(['a_a'], array_keys($workflow->getTransitions(['a_a'])));
-
-    // An empty array does not load all states.
-    $this->assertArrayEquals([], array_keys($workflow->getTransitions([])));
-  }
-
-
-  /**
-   * @covers ::getTransition
-   */
-  public function testGetTransition() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    // By default states are ordered in the order added.
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addState('archived', 'Archived')
-      ->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
-      ->addTransition('publish', 'Publish', ['draft'], 'published');
-
-    // Ensure we're returning state objects and they are set up correctly
-    $this->assertInstanceOf(Transition::class, $workflow->getTransition('create_new_draft'));
-    $this->assertEquals('publish', $workflow->getTransition('publish')->id());
-    $this->assertEquals('Publish', $workflow->getTransition('publish')->label());
-
-    $transition = $workflow->getTransition('publish');
-    $this->assertEquals($workflow->getState('draft'), $transition->from()['draft']);
-    $this->assertEquals($workflow->getState('published'), $transition->to());
-  }
-
-  /**
-   * @covers ::getTransition
-   */
-  public function testGetTransitionException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The transition 'transition_that_does_not_exist' does not exist in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->getTransition('transition_that_does_not_exist');
-  }
-
-  /**
-   * @covers ::getTransitionsForState
-   */
-  public function testGetTransitionsForState() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    // By default states are ordered in the order added.
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addState('archived', 'Archived')
-      ->addTransition('create_new_draft', 'Create new draft', ['archived', 'draft'], 'draft')
-      ->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
-      ->addTransition('archive', 'Archive', ['published'], 'archived');
-
-    $this->assertEquals(['create_new_draft', 'publish'], array_keys($workflow->getTransitionsForState('draft')));
-    $this->assertEquals(['create_new_draft'], array_keys($workflow->getTransitionsForState('draft', 'to')));
-    $this->assertEquals(['publish', 'archive'], array_keys($workflow->getTransitionsForState('published')));
-    $this->assertEquals(['publish'], array_keys($workflow->getTransitionsForState('published', 'to')));
-    $this->assertEquals(['create_new_draft'], array_keys($workflow->getTransitionsForState('archived', 'from')));
-    $this->assertEquals(['archive'], array_keys($workflow->getTransitionsForState('archived', 'to')));
-  }
-
-
-  /**
-   * @covers ::getTransitionFromStateToState
-   * @covers ::hasTransitionFromStateToState
-   */
-  public function testGetTransitionFromStateToState() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    // By default states are ordered in the order added.
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addState('archived', 'Archived')
-      ->addTransition('create_new_draft', 'Create new draft', ['archived', 'draft'], 'draft')
-      ->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
-      ->addTransition('archive', 'Archive', ['published'], 'archived');
-
-    $this->assertTrue($workflow->hasTransitionFromStateToState('draft', 'published'));
-    $this->assertFalse($workflow->hasTransitionFromStateToState('archived', 'archived'));
-    $transition = $workflow->getTransitionFromStateToState('published', 'archived');
-    $this->assertEquals('Archive', $transition->label());
-  }
-
-  /**
-   * @covers ::getTransitionFromStateToState
-   */
-  public function testGetTransitionFromStateToStateException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The transition from 'archived' to 'archived' does not exist in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    // By default states are ordered in the order added.
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addState('archived', 'Archived')
-      ->addTransition('create_new_draft', 'Create new draft', ['archived', 'draft'], 'draft')
-      ->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
-      ->addTransition('archive', 'Archive', ['published'], 'archived');
-
-    $workflow->getTransitionFromStateToState('archived', 'archived');
-  }
-
-  /**
-   * @covers ::setTransitionLabel
-   */
-  public function testSetTransitionLabel() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addTransition('publish', 'Publish', ['draft'], 'published');
-    $this->assertEquals('Publish', $workflow->getTransition('publish')->label());
-    $workflow->setTransitionLabel('publish', 'Publish!');
-    $this->assertEquals('Publish!', $workflow->getTransition('publish')->label());
-  }
-
-  /**
-   * @covers ::setTransitionLabel
-   */
-  public function testSetTransitionLabelException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->addState('published', 'Published');
-    $workflow->setTransitionLabel('draft-published', 'Publish');
-  }
-
-  /**
-   * @covers ::setTransitionWeight
-   */
-  public function testSetTransitionWeight() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addTransition('publish', 'Publish', ['draft'], 'published');
-    $this->assertEquals(0, $workflow->getTransition('publish')->weight());
-    $workflow->setTransitionWeight('publish', 10);
-    $this->assertEquals(10, $workflow->getTransition('publish')->weight());
-  }
-
-  /**
-   * @covers ::setTransitionWeight
-   */
-  public function testSetTransitionWeightException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->addState('published', 'Published');
-    $workflow->setTransitionWeight('draft-published', 10);
-  }
-
-  /**
-   * @covers ::setTransitionFromStates
-   */
-  public function testSetTransitionFromStates() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addState('archived', 'Archived')
-      ->addTransition('test', 'Test', ['draft'], 'draft');
-
-    $this->assertTrue($workflow->hasTransitionFromStateToState('draft', 'draft'));
-    $this->assertFalse($workflow->hasTransitionFromStateToState('published', 'draft'));
-    $this->assertFalse($workflow->hasTransitionFromStateToState('archived', 'draft'));
-    $workflow->setTransitionFromStates('test', ['draft', 'published', 'archived']);
-    $this->assertTrue($workflow->hasTransitionFromStateToState('draft', 'draft'));
-    $this->assertTrue($workflow->hasTransitionFromStateToState('published', 'draft'));
-    $this->assertTrue($workflow->hasTransitionFromStateToState('archived', 'draft'));
-    $workflow->setTransitionFromStates('test', ['published', 'archived']);
-    $this->assertFalse($workflow->hasTransitionFromStateToState('draft', 'draft'));
-    $this->assertTrue($workflow->hasTransitionFromStateToState('published', 'draft'));
-    $this->assertTrue($workflow->hasTransitionFromStateToState('archived', 'draft'));
-  }
-
-  /**
-   * @covers ::setTransitionFromStates
-   */
-  public function testSetTransitionFromStatesMissingTransition() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The transition 'test' does not exist in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addState('archived', 'Archived')
-      ->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft');
-
-    $workflow->setTransitionFromStates('test', ['draft', 'published', 'archived']);
-  }
-
-  /**
-   * @covers ::setTransitionFromStates
-   */
-  public function testSetTransitionFromStatesMissingState() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The state 'published' does not exist in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('archived', 'Archived')
-      ->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft');
-
-    $workflow->setTransitionFromStates('create_new_draft', ['draft', 'published', 'archived']);
-  }
-
-  /**
-   * @covers ::deleteTransition
-   */
-  public function testDeleteTransition() {
-    // Create a container so that the plugin manager and workflow type can be
-    // mocked and test that
-    // \Drupal\workflows\WorkflowTypeInterface::deleteState() is called
-    // correctly.
-    $container = new ContainerBuilder();
-    $workflow_type = $this->prophesize(WorkflowTypeInterface::class);
-    $workflow_type->decorateState(Argument::any())->willReturnArgument(0);
-    $workflow_type->decorateTransition(Argument::any())->willReturnArgument(0);
-    $workflow_type->deleteTransition('publish')->shouldBeCalled();
-    $workflow_manager = $this->prophesize(WorkflowTypeManager::class);
-    $workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal());
-    $container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
-    \Drupal::setContainer($container);
-
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow
-      ->addState('draft', 'Draft')
-      ->addState('published', 'Published')
-      ->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
-      ->addTransition('publish', 'Publish', ['draft'], 'published');
-    $this->assertTrue($workflow->getState('draft')->canTransitionTo('published'));
-    $workflow->deleteTransition('publish');
-    $this->assertFalse($workflow->getState('draft')->canTransitionTo('published'));
-    $this->assertTrue($workflow->getState('draft')->canTransitionTo('draft'));
-  }
-
-  /**
-   * @covers ::deleteTransition
-   */
-  public function testDeleteTransitionException() {
-    $this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow 'test'");
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $workflow->addState('published', 'Published');
-    $workflow->deleteTransition('draft-published');
-  }
-
-  /**
-   * @covers ::status
-   */
-  public function testStatus() {
-    $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
-    $this->assertFalse($workflow->status());
-    $workflow->addState('published', 'Published');
-    $this->assertTrue($workflow->status());
-  }
-
-}
diff --git a/core/modules/workflows/workflows.info.yml b/core/modules/workflows/workflows.info.yml
deleted file mode 100644
index 692d086..0000000
--- a/core/modules/workflows/workflows.info.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-name: 'Workflows'
-type: module
-description: 'Provides UI and API for managing workflows. This module can be used with the Content moderation module to add highly customisable workflows to content.'
-version: VERSION
-core: 8.x
-package: Core (Experimental)
-configure: workflows.overview
diff --git a/core/modules/workflows/workflows.links.action.yml b/core/modules/workflows/workflows.links.action.yml
deleted file mode 100644
index e3a80f7..0000000
--- a/core/modules/workflows/workflows.links.action.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-entity.workflow.add_form:
-  route_name: 'entity.workflow.add_form'
-  title: 'Add workflow'
-  appears_on:
-    - entity.workflow.collection
diff --git a/core/modules/workflows/workflows.links.menu.yml b/core/modules/workflows/workflows.links.menu.yml
deleted file mode 100644
index a6ac512..0000000
--- a/core/modules/workflows/workflows.links.menu.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-# Workflow menu items definition
-entity.workflow.collection:
-  title: 'Workflows'
-  route_name: entity.workflow.collection
-  description: 'Configure workflows.'
-  parent: system.admin_config_workflow
-
diff --git a/core/modules/workflows/workflows.module b/core/modules/workflows/workflows.module
deleted file mode 100644
index 26f72b4..0000000
--- a/core/modules/workflows/workflows.module
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-
-/**
- * @file
- * Provides hook implementations for the Workflow UI module.
- */
-
-use Drupal\Core\Routing\RouteMatchInterface;
-
-use Drupal\workflows\Form\WorkflowAddForm;
-use Drupal\workflows\Form\WorkflowEditForm;
-use Drupal\workflows\Form\WorkflowDeleteForm;
-use Drupal\workflows\Form\WorkflowStateAddForm;
-use Drupal\workflows\Form\WorkflowStateEditForm;
-use Drupal\workflows\Form\WorkflowStateDeleteForm;
-use Drupal\workflows\Form\WorkflowTransitionAddForm;
-use Drupal\workflows\Form\WorkflowTransitionEditForm;
-use Drupal\workflows\Form\WorkflowTransitionDeleteForm;
-use Drupal\workflows\WorkflowListBuilder;
-
-/**
- * Implements hook_help().
- */
-function workflows_help($route_name, RouteMatchInterface $route_match) {
-  switch ($route_name) {
-    // Main module help for the Workflow UI module.
-    case 'help.page.workflows':
-      $output = '';
-      $output .= '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('The Workflows module provides a UI and an API for creating workflows content. This lets site admins define workflows and their states, and then define transitions between those states. For more information, see the <a href=":workflow">online documentation for the Workflows module</a>.', [':workflow' => 'https://www.drupal.org/documentation/modules/workflows']) . '</p>';
-      return $output;
-  }
-}
-
-/**
- * Implements hook_entity_type_build().
- */
-function workflows_entity_type_build(array &$entity_types) {
-  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
-  $entity_types['workflow']
-    ->setFormClass('add', WorkflowAddForm::class)
-    ->setFormClass('edit', WorkflowEditForm::class)
-    ->setFormClass('delete', WorkflowDeleteForm::class)
-    ->setFormClass('add-state', WorkflowStateAddForm::class)
-    ->setFormClass('edit-state', WorkflowStateEditForm::class)
-    ->setFormClass('delete-state', WorkflowStateDeleteForm::class)
-    ->setFormClass('add-transition', WorkflowTransitionAddForm::class)
-    ->setFormClass('edit-transition', WorkflowTransitionEditForm::class)
-    ->setFormClass('delete-transition', WorkflowTransitionDeleteForm::class)
-    ->setListBuilderClass(WorkflowListBuilder::class)
-    ->set('admin_permission', 'administer workflows')
-    ->setLinkTemplate('add-form', '/admin/config/workflow/workflows/add')
-    ->setLinkTemplate('edit-form', '/admin/config/workflow/workflows/manage/{workflow}')
-    ->setLinkTemplate('delete-form', '/admin/config/workflow/workflows/manage/{workflow}/delete')
-    ->setLinkTemplate('add-state-form', '/admin/config/workflow/workflows/manage/{workflow}/add_state')
-    ->setLinkTemplate('add-transition-form', '/admin/config/workflow/workflows/manage/{workflow}/add_transition')
-    ->setLinkTemplate('collection', '/admin/config/workflow/workflows');
-}
diff --git a/core/modules/workflows/workflows.permissions.yml b/core/modules/workflows/workflows.permissions.yml
deleted file mode 100644
index 88573b6..0000000
--- a/core/modules/workflows/workflows.permissions.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-'administer workflows':
-  title: 'Administer workflows'
-  description: 'Create and edit workflows.'
-  'restrict access': TRUE
diff --git a/core/modules/workflows/workflows.routing.yml b/core/modules/workflows/workflows.routing.yml
deleted file mode 100644
index 02269cd..0000000
--- a/core/modules/workflows/workflows.routing.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-entity.workflow.add_state_form:
-  path: '/admin/config/workflow/workflows/manage/{workflow}/add_state'
-  defaults:
-    _entity_form: 'workflow.add-state'
-    _title: 'Add state'
-  requirements:
-    _entity_access: 'workflow.edit'
-
-entity.workflow.edit_state_form:
-  path: '/admin/config/workflow/workflows/manage/{workflow}/state/{workflow_state}'
-  defaults:
-    _entity_form: 'workflow.edit-state'
-    _title: 'Edit state'
-  requirements:
-    _entity_access: 'workflow.edit'
-
-entity.workflow.delete_state_form:
-  path: '/admin/config/workflow/workflows/manage/{workflow}/state/{workflow_state}/delete'
-  defaults:
-    _form: '\Drupal\workflows\Form\WorkflowStateDeleteForm'
-    _title: 'Delete state'
-  requirements:
-    _entity_access: 'workflow.delete-state'
-
-entity.workflow.add_transition_form:
-  path: '/admin/config/workflow/workflows/manage/{workflow}/add_transition'
-  defaults:
-    _entity_form: 'workflow.add-transition'
-    _title: 'Add state'
-  requirements:
-    _entity_access: 'workflow.edit'
-
-entity.workflow.edit_transition_form:
-  path: '/admin/config/workflow/workflows/manage/{workflow}/transition/{workflow_transition}'
-  defaults:
-    _entity_form: 'workflow.edit-transition'
-    _title: 'Edit state'
-  requirements:
-    _entity_access: 'workflow.edit'
-
-entity.workflow.delete_transition_form:
-  path: '/admin/config/workflow/workflows/manage/{workflow}/transition/{workflow_transition}/delete'
-  defaults:
-    _form: '\Drupal\workflows\Form\WorkflowTransitionDeleteForm'
-    _title: 'Delete state'
-  requirements:
-    _entity_access: 'workflow.edit'
\ No newline at end of file
diff --git a/core/modules/workflows/workflows.services.yml b/core/modules/workflows/workflows.services.yml
deleted file mode 100644
index 772bab7..0000000
--- a/core/modules/workflows/workflows.services.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-services:
-  plugin.manager.workflows.type:
-    class: Drupal\workflows\WorkflowTypeManager
-    parent: default_plugin_manager
-    tags:
-      - { name: plugin_manager_cache_clear }
\ No newline at end of file
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php b/core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php
index 94a9108..46f6017 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php
@@ -2,10 +2,6 @@
 
 namespace Drupal\FunctionalJavascriptTests;
 
-use Behat\Mink\Element\NodeElement;
-use Behat\Mink\Exception\ElementHtmlException;
-use Behat\Mink\Exception\ElementNotFoundException;
-use Behat\Mink\Exception\UnsupportedDriverActionException;
 use Drupal\Tests\WebAssert;
 
 /**
@@ -43,199 +39,4 @@ public function waitOnAutocomplete() {
     $this->assertWaitOnAjaxRequest();
   }
 
-  /**
-   * Test that a node, or it's specific corner, is visible in the viewport.
-   *
-   * Note: Always set the viewport size. This can be done with a PhantomJS
-   * startup parameter or in your test with \Behat\Mink\Session->resizeWindow().
-   * Drupal CI Javascript tests by default use a viewport of 1024x768px.
-   *
-   * @param string $selector_type
-   *   The element selector type (CSS, XPath).
-   * @param string|array $selector
-   *   The element selector. Note: the first found element is used.
-   * @param bool|string $corner
-   *   (Optional) The corner to test:
-   *   topLeft, topRight, bottomRight, bottomLeft.
-   *   Or FALSE to check the complete element (default).
-   * @param string $message
-   *   (optional) A message for the exception.
-   *
-   * @throws \Behat\Mink\Exception\ElementHtmlException
-   *   When the element doesn't exist.
-   * @throws \Behat\Mink\Exception\ElementNotFoundException
-   *   When the element is not visible in the viewport.
-   */
-  public function assertVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is not visible in the viewport.') {
-    $node = $this->session->getPage()->find($selector_type, $selector);
-    if ($node === NULL) {
-      if (is_array($selector)) {
-        $selector = implode(' ', $selector);
-      }
-      throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
-    }
-
-    // Check if the node is visible on the page, which is a prerequisite of
-    // being visible in the viewport.
-    if (!$node->isVisible()) {
-      throw new ElementHtmlException($message, $this->session->getDriver(), $node);
-    }
-
-    $result = $this->checkNodeVisibilityInViewport($node, $corner);
-
-    if (!$result) {
-      throw new ElementHtmlException($message, $this->session->getDriver(), $node);
-    }
-  }
-
-  /**
-   * Test that a node, or its specific corner, is not visible in the viewport.
-   *
-   * Note: the node should exist in the page, otherwise this assertion fails.
-   *
-   * @param string $selector_type
-   *   The element selector type (CSS, XPath).
-   * @param string|array $selector
-   *   The element selector. Note: the first found element is used.
-   * @param bool|string $corner
-   *   (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
-   *   Or FALSE to check the complete element (default).
-   * @param string $message
-   *   (optional) A message for the exception.
-   *
-   * @throws \Behat\Mink\Exception\ElementHtmlException
-   *   When the element doesn't exist.
-   * @throws \Behat\Mink\Exception\ElementNotFoundException
-   *   When the element is not visible in the viewport.
-   *
-   * @see \Drupal\FunctionalJavascriptTests\JSWebAssert::assertVisibleInViewport()
-   */
-  public function assertNotVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is visible in the viewport.') {
-    $node = $this->session->getPage()->find($selector_type, $selector);
-    if ($node === NULL) {
-      if (is_array($selector)) {
-        $selector = implode(' ', $selector);
-      }
-      throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
-    }
-
-    $result = $this->checkNodeVisibilityInViewport($node, $corner);
-
-    if ($result) {
-      throw new ElementHtmlException($message, $this->session->getDriver(), $node);
-    }
-  }
-
-  /**
-   * Check the visibility of a node, or it's specific corner.
-   *
-   * @param \Behat\Mink\Element\NodeElement $node
-   *   A valid node.
-   * @param bool|string $corner
-   *   (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
-   *   Or FALSE to check the complete element (default).
-   *
-   * @return bool
-   *   Returns TRUE if the node is visible in the viewport, FALSE otherwise.
-   *
-   * @throws \Behat\Mink\Exception\UnsupportedDriverActionException
-   *   When an invalid corner specification is given.
-   */
-  private function checkNodeVisibilityInViewport(NodeElement $node, $corner = FALSE) {
-    $xpath = $node->getXpath();
-
-    // Build the Javascript to test if the complete element or a specific corner
-    // is in the viewport.
-    switch ($corner) {
-      case 'topLeft':
-        $test_javascript_function = <<<JS
-          function t(r, lx, ly) {
-            return (
-              r.top >= 0 &&
-              r.top <= ly &&
-              r.left >= 0 &&
-              r.left <= lx
-            )
-          }
-JS;
-        break;
-
-      case 'topRight':
-        $test_javascript_function = <<<JS
-          function t(r, lx, ly) {
-            return (
-              r.top >= 0 &&
-              r.top <= ly &&
-              r.right >= 0 &&
-              r.right <= lx
-            );
-          }
-JS;
-        break;
-
-      case 'bottomRight':
-        $test_javascript_function = <<<JS
-          function t(r, lx, ly) {
-            return (
-              r.bottom >= 0 &&
-              r.bottom <= ly &&
-              r.right >= 0 &&
-              r.right <= lx
-            );
-          }
-JS;
-        break;
-
-      case 'bottomLeft':
-        $test_javascript_function = <<<JS
-          function t(r, lx, ly) {
-            return (
-              r.bottom >= 0 &&
-              r.bottom <= ly &&
-              r.left >= 0 &&
-              r.left <= lx
-            );
-          }
-JS;
-        break;
-
-      case FALSE:
-        $test_javascript_function = <<<JS
-          function t(r, lx, ly) {
-            return (
-              r.top >= 0 &&
-              r.left >= 0 &&
-              r.bottom <= ly &&
-              r.right <= lx
-            );
-          }
-JS;
-        break;
-
-      // Throw an exception if an invalid corner parameter is given.
-      default:
-        throw new UnsupportedDriverActionException($corner, $this->session->getDriver());
-    }
-
-    // Build the full Javascript test. The shared logic gets the corner
-    // specific test logic injected.
-    $full_javascript_visibility_test = <<<JS
-      (function(t){
-        var w = window,
-        d = document,
-        e = d.documentElement,
-        n = d.evaluate("$xpath", d, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue,
-        r = n.getBoundingClientRect(),
-        lx = (w.innerWidth || e.clientWidth),
-        ly = (w.innerHeight || e.clientHeight);
-
-        return t(r, lx, ly);
-      }($test_javascript_function));
-JS;
-
-    // Check the visibility by injecting and executing the full Javascript test
-    // script in the page.
-    return $this->session->evaluateScript($full_javascript_visibility_test);
-  }
-
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
index e49ee7e..23cf034 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
@@ -102,9 +102,6 @@ public function testEntityTypeUpdateWithoutData() {
     $expected = array(
       'entity_test_update' => array(
         t('The %entity_type entity type needs to be updated.', ['%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel()]),
-        // The revision key is now defined, so the revision field needs to be
-        // created.
-        t('The %field_name field needs to be installed.', ['%field_name' => 'Revision ID']),
       ),
     );
     $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected); //, 'EntityDefinitionUpdateManager reports the expected change summary.');
@@ -800,7 +797,7 @@ public function testBaseFieldEntityKeyUpdateWithExistingData() {
   /**
    * Check that field schema is correctly handled with long-named fields.
    */
-  public function testLongNameFieldIndexes() {
+  function testLongNameFieldIndexes() {
     $this->addLongNameBaseField();
     $entity_type_id = 'entity_test_update';
     $entity_type = $this->entityManager->getDefinition($entity_type_id);
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php
index 5dc1a94..56af888 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php
@@ -3,7 +3,6 @@
 namespace Drupal\KernelTests\Core\Entity;
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\RevisionLogInterface;
 use Drupal\Core\Entity\TypedData\EntityDataDefinition;
 use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -573,12 +572,6 @@ protected function doTestDataStructureInterfaces($entity_type) {
       // Field format.
       NULL,
     );
-
-    if ($entity instanceof RevisionLogInterface) {
-      // Adding empty string for revision message.
-      $target_strings[] = '';
-    }
-
     asort($strings);
     asort($target_strings);
     $this->assertEqual(array_values($strings), array_values($target_strings), format_string('%entity_type: All contained strings found.', array('%entity_type' => $entity_type)));
diff --git a/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php b/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php
index c3e7af6..deaf033 100644
--- a/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php
@@ -83,11 +83,11 @@ public function testInlineTemplate() {
     $this->assertEqual($renderer->renderRoot($element_copy), $expected);
 
     $name = '{# inline_template_start #}' . $element['test']['#template'];
-    $prefix = $environment->getTwigCachePrefix();
+    $hash = $this->container->getParameter('twig_extension_hash');
 
     $cache = $environment->getCache();
     $class = $environment->getTemplateClass($name);
-    $expected = $prefix . '_inline-template' . '_' . hash('sha256', $class);
+    $expected = $hash . '_inline-template' . '_' . hash('sha256', $class);
     $this->assertEqual($expected, $cache->generateKey($name, $class));
   }
 
diff --git a/core/tests/Drupal/Tests/BrowserTestBase.php b/core/tests/Drupal/Tests/BrowserTestBase.php
index 33806d3..1ab59b6 100644
--- a/core/tests/Drupal/Tests/BrowserTestBase.php
+++ b/core/tests/Drupal/Tests/BrowserTestBase.php
@@ -7,20 +7,25 @@
 use Behat\Mink\Mink;
 use Behat\Mink\Selector\SelectorsHandler;
 use Behat\Mink\Session;
+use Drupal\Component\FileCache\FileCacheFactory;
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Config\Development\ConfigSchemaChecker;
 use Drupal\Core\Database\Database;
+use Drupal\Core\DrupalKernel;
+use Drupal\Core\Serialization\Yaml;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Session\AnonymousUserSession;
+use Drupal\Core\Session\UserSession;
 use Drupal\Core\Site\Settings;
 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
-use Drupal\Core\Test\FunctionalTestSetupTrait;
 use Drupal\Core\Test\TestRunnerKernel;
-use Drupal\Core\Test\TestSetupTrait;
 use Drupal\Core\Url;
 use Drupal\Core\Utility\Error;
+use Drupal\Core\Test\TestDatabase;
 use Drupal\FunctionalTests\AssertLegacyTrait;
 use Drupal\simpletest\AssertHelperTrait;
 use Drupal\simpletest\ContentTypeCreationTrait;
@@ -42,9 +47,6 @@
  * @ingroup testing
  */
 abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
-
-  use FunctionalTestSetupTrait;
-  use TestSetupTrait;
   use AssertHelperTrait;
   use BlockCreationTrait {
     placeBlock as drupalPlaceBlock;
@@ -67,6 +69,20 @@
   use XdebugRequestTrait;
 
   /**
+   * Class loader.
+   *
+   * @var object
+   */
+  protected $classLoader;
+
+  /**
+   * The site directory of this test run.
+   *
+   * @var string
+   */
+  protected $siteDirectory;
+
+  /**
    * The database prefix of this test run.
    *
    * @var string
@@ -88,6 +104,33 @@
   protected $timeLimit = 500;
 
   /**
+   * The public file directory for the test environment.
+   *
+   * This is set in BrowserTestBase::prepareEnvironment().
+   *
+   * @var string
+   */
+  protected $publicFilesDirectory;
+
+  /**
+   * The private file directory for the test environment.
+   *
+   * This is set in BrowserTestBase::prepareEnvironment().
+   *
+   * @var string
+   */
+  protected $privateFilesDirectory;
+
+  /**
+   * The temp file directory for the test environment.
+   *
+   * This is set in BrowserTestBase::prepareEnvironment().
+   *
+   * @var string
+   */
+  protected $tempFilesDirectory;
+
+  /**
    * The translation file directory for the test environment.
    *
    * This is set in BrowserTestBase::prepareEnvironment().
@@ -97,6 +140,20 @@
   protected $translationFilesDirectory;
 
   /**
+   * The DrupalKernel instance used in the test.
+   *
+   * @var \Drupal\Core\DrupalKernel
+   */
+  protected $kernel;
+
+  /**
+   * The dependency injection container used in the test.
+   *
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
+   */
+  protected $container;
+
+  /**
    * The config importer that can be used in a test.
    *
    * @var \Drupal\Core\Config\ConfigImporter
@@ -104,6 +161,15 @@
   protected $configImporter;
 
   /**
+   * Set to TRUE to strict check all configuration saved.
+   *
+   * @see \Drupal\Core\Config\Development\ConfigSchemaChecker
+   *
+   * @var bool
+   */
+  protected $strictConfigSchema = TRUE;
+
+  /**
    * Modules to enable.
    *
    * The test runner will merge the $modules lists from this class, the class
@@ -117,6 +183,22 @@
   protected static $modules = [];
 
   /**
+   * An array of config object names that are excluded from schema checking.
+   *
+   * @var string[]
+   */
+  protected static $configSchemaCheckerExclusions = array(
+    // Following are used to test lack of or partial schema. Where partial
+    // schema is provided, that is explicitly tested in specific tests.
+    'config_schema_test.noschema',
+    'config_schema_test.someschema',
+    'config_schema_test.schema_data_types',
+    'config_schema_test.no_schema_data_types',
+    // Used to test application of schema to filtering of configuration.
+    'config_test.dynamic.system',
+  );
+
+  /**
    * The profile to install as a basis for testing.
    *
    * @var string
@@ -131,6 +213,20 @@
   protected $loggedInUser = FALSE;
 
   /**
+   * The root user.
+   *
+   * @var \Drupal\Core\Session\UserSession
+   */
+  protected $rootUser;
+
+  /**
+   * The config directories used in this test.
+   *
+   * @var array
+   */
+  protected $configDirectories = array();
+
+  /**
    * An array of custom translations suitable for drupal_rewrite_settings().
    *
    * @var array
@@ -941,14 +1037,161 @@ protected function getOptions($select, Element $container = NULL) {
    * Installs Drupal into the Simpletest site.
    */
   public function installDrupal() {
-    $this->initUserSession();
-    $this->prepareSettings();
-    $this->doInstall();
-    $this->initSettings();
-    $container = $this->initKernel(\Drupal::request());
-    $this->initConfig($container);
-    $this->installModulesFromClassProperty($container);
-    $this->rebuildAll();
+    // Define information about the user 1 account.
+    $this->rootUser = new UserSession(array(
+      'uid' => 1,
+      'name' => 'admin',
+      'mail' => 'admin@example.com',
+      'passRaw' => $this->randomMachineName(),
+    ));
+
+    // The child site derives its session name from the database prefix when
+    // running web tests.
+    $this->generateSessionName($this->databasePrefix);
+
+    // Get parameters for install_drupal() before removing global variables.
+    $parameters = $this->installParameters();
+
+    // Prepare installer settings that are not install_drupal() parameters.
+    // Copy and prepare an actual settings.php, so as to resemble a regular
+    // installation.
+    // Not using File API; a potential error must trigger a PHP warning.
+    $directory = DRUPAL_ROOT . '/' . $this->siteDirectory;
+    copy(DRUPAL_ROOT . '/sites/default/default.settings.php', $directory . '/settings.php');
+
+    // All file system paths are created by System module during installation.
+    // @see system_requirements()
+    // @see TestBase::prepareEnvironment()
+    $settings['settings']['file_public_path'] = (object) array(
+      'value' => $this->publicFilesDirectory,
+      'required' => TRUE,
+    );
+    $settings['settings']['file_private_path'] = (object) [
+      'value' => $this->privateFilesDirectory,
+      'required' => TRUE,
+    ];
+    $this->writeSettings($settings);
+    // Allow for test-specific overrides.
+    $settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSiteDirectory . '/settings.testing.php';
+    if (file_exists($settings_testing_file)) {
+      // Copy the testing-specific settings.php overrides in place.
+      copy($settings_testing_file, $directory . '/settings.testing.php');
+      // Add the name of the testing class to settings.php and include the
+      // testing specific overrides.
+      file_put_contents($directory . '/settings.php', "\n\$test_class = '" . get_class($this) . "';\n" . 'include DRUPAL_ROOT . \'/\' . $site_path . \'/settings.testing.php\';' . "\n", FILE_APPEND);
+    }
+
+    $settings_services_file = DRUPAL_ROOT . '/' . $this->originalSiteDirectory . '/testing.services.yml';
+    if (!file_exists($settings_services_file)) {
+      // Otherwise, use the default services as a starting point for overrides.
+      $settings_services_file = DRUPAL_ROOT . '/sites/default/default.services.yml';
+    }
+    // Copy the testing-specific service overrides in place.
+    copy($settings_services_file, $directory . '/services.yml');
+    if ($this->strictConfigSchema) {
+      // Add a listener to validate configuration schema on save.
+      $content = file_get_contents($directory . '/services.yml');
+      $services = Yaml::decode($content);
+      $services['services']['simpletest.config_schema_checker'] = [
+        'class' => ConfigSchemaChecker::class,
+        'arguments' => ['@config.typed', $this->getConfigSchemaExclusions()],
+        'tags' => [['name' => 'event_subscriber']]
+      ];
+      file_put_contents($directory . '/services.yml', Yaml::encode($services));
+    }
+
+    // Since Drupal is bootstrapped already, install_begin_request() will not
+    // bootstrap into DRUPAL_BOOTSTRAP_CONFIGURATION (again). Hence, we have to
+    // reload the newly written custom settings.php manually.
+    Settings::initialize(DRUPAL_ROOT, $directory, $this->classLoader);
+
+    // Execute the non-interactive installer.
+    require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
+    install_drupal($parameters);
+
+    // Import new settings.php written by the installer.
+    Settings::initialize(DRUPAL_ROOT, $directory, $this->classLoader);
+    foreach ($GLOBALS['config_directories'] as $type => $path) {
+      $this->configDirectories[$type] = $path;
+    }
+
+    // After writing settings.php, the installer removes write permissions from
+    // the site directory. To allow drupal_generate_test_ua() to write a file
+    // containing the private key for drupal_valid_test_ua(), the site directory
+    // has to be writable.
+    // TestBase::restoreEnvironment() will delete the entire site directory. Not
+    // using File API; a potential error must trigger a PHP warning.
+    chmod($directory, 0777);
+
+    // During tests, cacheable responses should get the debugging cacheability
+    // headers by default.
+    $this->setContainerParameter('http.response.debug_cacheability_headers', TRUE);
+
+    $request = \Drupal::request();
+    $this->kernel = DrupalKernel::createFromRequest($request, $this->classLoader, 'prod', TRUE);
+    $this->kernel->prepareLegacyRequest($request);
+    // Force the container to be built from scratch instead of loaded from the
+    // disk. This forces us to not accidentally load the parent site.
+    $container = $this->kernel->rebuildContainer();
+
+    $config = $container->get('config.factory');
+
+    // Manually create and configure private and temporary files directories.
+    file_prepare_directory($this->privateFilesDirectory, FILE_CREATE_DIRECTORY);
+    file_prepare_directory($this->tempFilesDirectory, FILE_CREATE_DIRECTORY);
+    // While the temporary files path could be preset/enforced in settings.php
+    // like the public files directory above, some tests expect it to be
+    // configurable in the UI. If declared in settings.php, it would no longer
+    // be configurable.
+    $config->getEditable('system.file')
+      ->set('path.temporary', $this->tempFilesDirectory)
+      ->save();
+
+    // Manually configure the test mail collector implementation to prevent
+    // tests from sending out emails and collect them in state instead.
+    // While this should be enforced via settings.php prior to installation,
+    // some tests expect to be able to test mail system implementations.
+    $config->getEditable('system.mail')
+      ->set('interface.default', 'test_mail_collector')
+      ->save();
+
+    // By default, verbosely display all errors and disable all production
+    // environment optimizations for all tests to avoid needless overhead and
+    // ensure a sane default experience for test authors.
+    // @see https://www.drupal.org/node/2259167
+    $config->getEditable('system.logging')
+      ->set('error_level', 'verbose')
+      ->save();
+    $config->getEditable('system.performance')
+      ->set('css.preprocess', FALSE)
+      ->set('js.preprocess', FALSE)
+      ->save();
+
+    // Collect modules to install.
+    $class = get_class($this);
+    $modules = array();
+    while ($class) {
+      if (property_exists($class, 'modules')) {
+        $modules = array_merge($modules, $class::$modules);
+      }
+      $class = get_parent_class($class);
+    }
+    if ($modules) {
+      $modules = array_unique($modules);
+      $success = $container->get('module_installer')->install($modules, TRUE);
+      $this->assertTrue($success, SafeMarkup::format('Enabled modules: %modules', array('%modules' => implode(', ', $modules))));
+      $this->rebuildContainer();
+    }
+
+    // Reset/rebuild all data structures after enabling the modules, primarily
+    // to synchronize all data structures and caches between the test runner and
+    // the child site.
+    // Affects e.g. StreamWrapperManagerInterface::getWrappers().
+    // @see \Drupal\Core\DrupalKernel::bootCode()
+    // @todo Test-specific setUp() methods may set up further fixtures; find a
+    //   way to execute this after setUp() is done, or to eliminate it entirely.
+    $this->resetAll();
+    $this->kernel->prepareLegacyRequest($request);
   }
 
   /**
@@ -983,8 +1226,8 @@ protected function installParameters() {
             'name' => $this->rootUser->name,
             'mail' => $this->rootUser->getEmail(),
             'pass' => array(
-              'pass1' => $this->rootUser->pass_raw,
-              'pass2' => $this->rootUser->pass_raw,
+              'pass1' => $this->rootUser->passRaw,
+              'pass2' => $this->rootUser->passRaw,
             ),
           ),
           // form_type_checkboxes_value() requires NULL instead of FALSE values
@@ -1000,6 +1243,65 @@ protected function installParameters() {
   }
 
   /**
+   * Generates a database prefix for running tests.
+   *
+   * The database prefix is used by prepareEnvironment() to setup a public files
+   * directory for the test to be run, which also contains the PHP error log,
+   * which is written to in case of a fatal error. Since that directory is based
+   * on the database prefix, all tests (even unit tests) need to have one, in
+   * order to access and read the error log.
+   *
+   * The generated database table prefix is used for the Drupal installation
+   * being performed for the test. It is also used by the cookie value of
+   * SIMPLETEST_USER_AGENT by the Mink controlled browser. During early Drupal
+   * bootstrap, the cookie is parsed, and if it matches, all database queries
+   * use the database table prefix that has been generated here.
+   *
+   * @see drupal_valid_test_ua()
+   * @see BrowserTestBase::prepareEnvironment()
+   */
+  private function prepareDatabasePrefix() {
+    $test_db = new TestDatabase();
+    $this->siteDirectory = $test_db->getTestSitePath();
+    $this->databasePrefix = $test_db->getDatabasePrefix();
+  }
+
+  /**
+   * Changes the database connection to the prefixed one.
+   *
+   * @see BrowserTestBase::prepareEnvironment()
+   */
+  private function changeDatabasePrefix() {
+    if (empty($this->databasePrefix)) {
+      $this->prepareDatabasePrefix();
+    }
+
+    // If the test is run with argument dburl then use it.
+    $db_url = getenv('SIMPLETEST_DB');
+    if (!empty($db_url)) {
+      $database = Database::convertDbUrlToConnectionInfo($db_url, DRUPAL_ROOT);
+      Database::addConnectionInfo('default', 'default', $database);
+    }
+
+    // Clone the current connection and replace the current prefix.
+    $connection_info = Database::getConnectionInfo('default');
+    if (is_null($connection_info)) {
+      throw new \InvalidArgumentException('There is no database connection so no tests can be run. You must provide a SIMPLETEST_DB environment variable to run PHPUnit based functional tests outside of run-tests.sh.');
+    }
+    else {
+      Database::renameConnection('default', 'simpletest_original_default');
+      foreach ($connection_info as $target => $value) {
+        // Replace the full table prefix definition to ensure that no table
+        // prefixes of the test runner leak into the test.
+        $connection_info[$target]['prefix'] = array(
+          'default' => $value['prefix']['default'] . $this->databasePrefix,
+        );
+      }
+      Database::addConnectionInfo('default', 'default', $connection_info['default']);
+    }
+  }
+
+  /**
    * Prepares the current environment for running the test.
    *
    * Also sets up new resources for the testing environment, such as the public
@@ -1082,6 +1384,163 @@ protected function prepareEnvironment() {
   }
 
   /**
+   * Returns the database connection to the site running Simpletest.
+   *
+   * @return \Drupal\Core\Database\Connection
+   *   The database connection to use for inserting assertions.
+   */
+  public static function getDatabaseConnection() {
+    return TestDatabase::getConnection();
+  }
+
+  /**
+   * Rewrites the settings.php file of the test site.
+   *
+   * @param array $settings
+   *   An array of settings to write out, in the format expected by
+   *   drupal_rewrite_settings().
+   *
+   * @see drupal_rewrite_settings()
+   */
+  protected function writeSettings(array $settings) {
+    include_once DRUPAL_ROOT . '/core/includes/install.inc';
+    $filename = $this->siteDirectory . '/settings.php';
+
+    // system_requirements() removes write permissions from settings.php
+    // whenever it is invoked.
+    // Not using File API; a potential error must trigger a PHP warning.
+    chmod($filename, 0666);
+    drupal_rewrite_settings($settings, $filename);
+  }
+
+  /**
+   * Rebuilds \Drupal::getContainer().
+   *
+   * Use this to build a new kernel and service container. For example, when the
+   * list of enabled modules is changed via the Mink controlled browser, in
+   * which case the test process still contains an old kernel and service
+   * container with an old module list.
+   *
+   * @see BrowserTestBase::prepareEnvironment()
+   * @see BrowserTestBase::restoreEnvironment()
+   *
+   * @todo Fix https://www.drupal.org/node/2021959 so that module enable/disable
+   *   changes are immediately reflected in \Drupal::getContainer(). Until then,
+   *   tests can invoke this workaround when requiring services from newly
+   *   enabled modules to be immediately available in the same request.
+   */
+  protected function rebuildContainer() {
+    // Rebuild the kernel and bring it back to a fully bootstrapped state.
+    $this->container = $this->kernel->rebuildContainer();
+
+    // Make sure the url generator has a request object, otherwise calls to
+    // $this->drupalGet() will fail.
+    $this->prepareRequestForGenerator();
+  }
+
+  /**
+   * Creates a mock request and sets it on the generator.
+   *
+   * This is used to manipulate how the generator generates paths during tests.
+   * It also ensures that calls to $this->drupalGet() will work when running
+   * from run-tests.sh because the url generator no longer looks at the global
+   * variables that are set there but relies on getting this information from a
+   * request object.
+   *
+   * @param bool $clean_urls
+   *   Whether to mock the request using clean urls.
+   * @param array $override_server_vars
+   *   An array of server variables to override.
+   *
+   * @return Request
+   *   The mocked request object.
+   */
+  protected function prepareRequestForGenerator($clean_urls = TRUE, $override_server_vars = array()) {
+    $request = Request::createFromGlobals();
+    $server = $request->server->all();
+    if (basename($server['SCRIPT_FILENAME']) != basename($server['SCRIPT_NAME'])) {
+      // We need this for when the test is executed by run-tests.sh.
+      // @todo Remove this once run-tests.sh has been converted to use a Request
+      //   object.
+      $cwd = getcwd();
+      $server['SCRIPT_FILENAME'] = $cwd . '/' . basename($server['SCRIPT_NAME']);
+      $base_path = rtrim($server['REQUEST_URI'], '/');
+    }
+    else {
+      $base_path = $request->getBasePath();
+    }
+    if ($clean_urls) {
+      $request_path = $base_path ? $base_path . '/user' : 'user';
+    }
+    else {
+      $request_path = $base_path ? $base_path . '/index.php/user' : '/index.php/user';
+    }
+    $server = array_merge($server, $override_server_vars);
+
+    $request = Request::create($request_path, 'GET', array(), array(), array(), $server);
+    // Ensure the request time is REQUEST_TIME to ensure that API calls
+    // in the test use the right timestamp.
+    $request->server->set('REQUEST_TIME', REQUEST_TIME);
+    $this->container->get('request_stack')->push($request);
+
+    // The request context is normally set by the router_listener from within
+    // its KernelEvents::REQUEST listener. In the Simpletest parent site this
+    // event is not fired, therefore it is necessary to updated the request
+    // context manually here.
+    $this->container->get('router.request_context')->fromRequest($request);
+
+    return $request;
+  }
+
+  /**
+   * Resets all data structures after having enabled new modules.
+   *
+   * This method is called by \Drupal\simpletest\BrowserTestBase::setUp() after
+   * enabling the requested modules. It must be called again when additional
+   * modules are enabled later.
+   */
+  protected function resetAll() {
+    // Clear all database and static caches and rebuild data structures.
+    drupal_flush_all_caches();
+    $this->container = \Drupal::getContainer();
+
+    // Reset static variables and reload permissions.
+    $this->refreshVariables();
+  }
+
+  /**
+   * Refreshes in-memory configuration and state information.
+   *
+   * Useful after a page request is made that changes configuration or state in
+   * a different thread.
+   *
+   * In other words calling a settings page with $this->submitForm() with a
+   * changed value would update configuration to reflect that change, but in the
+   * thread that made the call (thread running the test) the changed values
+   * would not be picked up.
+   *
+   * This method clears the cache and loads a fresh copy.
+   */
+  protected function refreshVariables() {
+    // Clear the tag cache.
+    $this->container->get('cache_tags.invalidator')->resetChecksums();
+    // @todo Replace drupal_static() usage within classes and provide a
+    //   proper interface for invoking reset() on a cache backend:
+    //   https://www.drupal.org/node/2311945.
+    drupal_static_reset('Drupal\Core\Cache\CacheBackendInterface::tagCache');
+    drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::deletedTags');
+    drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::invalidatedTags');
+    foreach (Cache::getBins() as $backend) {
+      if (is_callable(array($backend, 'reset'))) {
+        $backend->reset();
+      }
+    }
+
+    $this->container->get('config.factory')->reset();
+    $this->container->get('state')->resetCache();
+  }
+
+  /**
    * Returns whether a given user account is logged in.
    *
    * @param \Drupal\Core\Session\AccountInterface $account
@@ -1341,6 +1800,46 @@ public static function assertEquals($expected, $actual, $message = '', $delta =
   }
 
   /**
+   * Changes parameters in the services.yml file.
+   *
+   * @param string $name
+   *   The name of the parameter.
+   * @param mixed $value
+   *   The value of the parameter.
+   */
+  protected function setContainerParameter($name, $value) {
+    $filename = $this->siteDirectory . '/services.yml';
+    chmod($filename, 0666);
+
+    $services = Yaml::decode(file_get_contents($filename));
+    $services['parameters'][$name] = $value;
+    file_put_contents($filename, Yaml::encode($services));
+
+    // Ensure that the cache is deleted for the yaml file loader.
+    $file_cache = FileCacheFactory::get('container_yaml_loader');
+    $file_cache->delete($filename);
+  }
+
+  /**
+   * Gets the config schema exclusions for this test.
+   *
+   * @return string[]
+   *   An array of config object names that are excluded from schema checking.
+   */
+  protected function getConfigSchemaExclusions() {
+    $class = get_class($this);
+    $exceptions = [];
+    while ($class) {
+      if (property_exists($class, 'configSchemaCheckerExclusions')) {
+        $exceptions = array_merge($exceptions, $class::$configSchemaCheckerExclusions);
+      }
+      $class = get_parent_class($class);
+    }
+    // Filter out any duplicates.
+    return array_unique($exceptions);
+  }
+
+  /**
    * Retrieves the current calling line in the class under test.
    *
    * @return array
diff --git a/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php b/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php
index c818bc6..3c89804 100644
--- a/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php
+++ b/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\Tests\Component\DependencyInjection;
 
-use Drupal\Component\Utility\Crypt;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
 use Prophecy\Argument;
@@ -1001,7 +1000,7 @@ protected function getParameterCall($name) {
    */
   protected function getPrivateServiceCall($id, $service_definition, $shared = FALSE) {
     if (!$id) {
-      $hash = Crypt::hashBase64(serialize($service_definition));
+      $hash = sha1(serialize($service_definition));
       $id = 'private__' . $hash;
     }
     return (object) array(
diff --git a/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php b/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php
index 5a6b319..49b6556 100644
--- a/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php
+++ b/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\Tests\Component\DependencyInjection\Dumper {
 
-  use Drupal\Component\Utility\Crypt;
   use Symfony\Component\DependencyInjection\Definition;
   use Symfony\Component\DependencyInjection\Reference;
   use Symfony\Component\DependencyInjection\Parameter;
@@ -626,7 +625,7 @@ public function testGetServiceDefinitionForResource() {
      */
     protected function getPrivateServiceCall($id, $service_definition, $shared = FALSE) {
       if (!$id) {
-        $hash = Crypt::hashBase64(serialize($service_definition));
+        $hash = sha1(serialize($service_definition));
         $id = 'private__' . $hash;
       }
       return (object) array(
diff --git a/core/tests/Drupal/Tests/Component/Utility/XssTest.php b/core/tests/Drupal/Tests/Component/Utility/XssTest.php
index c41bf22..0b2f51e 100644
--- a/core/tests/Drupal/Tests/Component/Utility/XssTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/XssTest.php
@@ -506,6 +506,18 @@ public function providerTestAttributes() {
         array('img')
       ),
       array(
+        '<a href="https://www.drupal.org/" rel="dc:publisher">Drupal</a>',
+        '<a href="https://www.drupal.org/" rel="dc:publisher">Drupal</a>',
+        'Link tag with rel attribute',
+        array('a')
+      ),
+      array(
+        '<span property="dc:subject">Drupal 8: The best release ever.</span>',
+        '<span property="dc:subject">Drupal 8: The best release ever.</span>',
+        'Span tag with property attribute',
+        array('span')
+      ),
+      array(
         '<img src="http://example.com/foo.jpg" data-caption="Drupal 8: The best release ever.">',
         '<img src="http://example.com/foo.jpg" data-caption="Drupal 8: The best release ever.">',
         'Image tag with data attribute',
diff --git a/core/tests/Drupal/Tests/Core/Access/RouteProcessorCsrfTest.php b/core/tests/Drupal/Tests/Core/Access/RouteProcessorCsrfTest.php
index 1a36c3d..d1f8f9e 100644
--- a/core/tests/Drupal/Tests/Core/Access/RouteProcessorCsrfTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/RouteProcessorCsrfTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Tests\Core\Access;
 
-use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\Tests\UnitTestCase;
 use Drupal\Core\Access\RouteProcessorCsrf;
@@ -69,7 +68,7 @@ public function testProcessOutbound() {
     // Bubbleable metadata of routes with a _csrf_token route requirement is a
     // placeholder.
     $path = 'test-path';
-    $placeholder = Crypt::hashBase64($path);
+    $placeholder = hash('sha1', $path);
     $placeholder_render_array = [
       '#lazy_builder' => ['route_processor_csrf:renderPlaceholderCsrfToken', [$path]],
     ];
@@ -89,7 +88,7 @@ public function testProcessOutboundDynamicOne() {
     // Bubbleable metadata of routes with a _csrf_token route requirement is a
     // placeholder.
     $path = 'test-path/100';
-    $placeholder = Crypt::hashBase64($path);
+    $placeholder = hash('sha1', $path);
     $placeholder_render_array = [
       '#lazy_builder' => ['route_processor_csrf:renderPlaceholderCsrfToken', [$path]],
     ];
@@ -108,7 +107,7 @@ public function testProcessOutboundDynamicTwo() {
     // Bubbleable metadata of routes with a _csrf_token route requirement is a
     // placeholder.
     $path = '100/test-path/test';
-    $placeholder = Crypt::hashBase64($path);
+    $placeholder = hash('sha1', $path);
     $placeholder_render_array = [
       '#lazy_builder' => ['route_processor_csrf:renderPlaceholderCsrfToken', [$path]],
     ];
diff --git a/core/tests/Drupal/Tests/Core/Render/RendererPlaceholdersTest.php b/core/tests/Drupal/Tests/Core/Render/RendererPlaceholdersTest.php
index 12ab955..530f603 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererPlaceholdersTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererPlaceholdersTest.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\Tests\Core\Render;
 
-use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Render\Markup;
@@ -72,7 +71,7 @@ public function providerPlaceholders() {
       if (is_array($cache_keys)) {
         $token_render_array['#cache']['keys'] = $cache_keys;
       }
-      $token = Crypt::hashBase64(serialize($token_render_array));
+      $token = hash('crc32b', serialize($token_render_array));
       // \Drupal\Core\Render\Markup::create() is necessary as the render
       // system would mangle this markup. As this is exactly what happens at
       // runtime this is a valid use-case.
@@ -619,7 +618,7 @@ public function testCacheableParent($test_element, $args, array $expected_placeh
 
     $this->setUpRequest('GET');
 
-    $token = Crypt::hashBase64(serialize($expected_placeholder_render_array));
+    $token = hash('crc32b', serialize($expected_placeholder_render_array));
     $placeholder_callback = $expected_placeholder_render_array['#lazy_builder'][0];
     $expected_placeholder_markup = '<drupal-render-placeholder callback="' . $placeholder_callback . '" arguments="0=' . $args[0] . '" token="' . $token . '"></drupal-render-placeholder>';
     $this->assertSame($expected_placeholder_markup, Html::normalize($expected_placeholder_markup), 'Placeholder unaltered by Html::normalize() which is used by FilterHtmlCorrector.');
diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
index bed7d3d..51cfde8 100644
--- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php
@@ -140,6 +140,32 @@ public function providerTestRenderBasic() {
       '#children' => 'foo',
       'child' => ['#markup' => 'bar'],
     ], 'foo'];
+    // Ensure that content added to #markup via a #pre_render callback is safe.
+    $data[] = [[
+      '#markup' => 'foo',
+      '#pre_render' => [function($elements) {
+        $elements['#markup'] .= '<script>alert("bar");</script>';
+        return $elements;
+      }]
+    ], 'fooalert("bar");'];
+    // Test #allowed_tags in combination with #markup and #pre_render.
+    $data[] = [[
+      '#markup' => 'foo',
+      '#allowed_tags' => array('script'),
+      '#pre_render' => [function($elements) {
+        $elements['#markup'] .= '<script>alert("bar");</script>';
+        return $elements;
+      }]
+    ], 'foo<script>alert("bar");</script>'];
+    // Ensure output is escaped when adding content to #check_plain through
+    // a #pre_render callback.
+    $data[] = [[
+      '#plain_text' => 'foo',
+      '#pre_render' => [function($elements) {
+        $elements['#plain_text'] .= '<script>alert("bar");</script>';
+        return $elements;
+      }]
+    ], 'foo&lt;script&gt;alert(&quot;bar&quot;);&lt;/script&gt;'];
 
     // Part 2: render arrays using #theme and #theme_wrappers.
 
