diff --git a/core/modules/layout_builder/config/schema/layout_builder.schema.yml b/core/modules/layout_builder/config/schema/layout_builder.schema.yml
index 682caa78c4..287215c8e9 100644
--- a/core/modules/layout_builder/config/schema/layout_builder.schema.yml
+++ b/core/modules/layout_builder/config/schema/layout_builder.schema.yml
@@ -44,3 +44,20 @@ layout_builder.component:
     additional:
       type: ignore
       label: 'Additional data'
+
+inline_block_contents:
+  type: block_settings
+  label: 'Inline content block'
+  mapping:
+    view_mode:
+      type: string
+      lable: 'View mode'
+    block_revision_id:
+      type: integer
+      label: 'Block revision ID'
+    block_serialized:
+      type: string
+      label: 'Serialized block'
+
+block.settings.inline_block_content:*:
+  type: inline_block_contents
diff --git a/core/modules/layout_builder/layout_builder.install b/core/modules/layout_builder/layout_builder.install
index acb1e4fdf3..42cf53f675 100644
--- a/core/modules/layout_builder/layout_builder.install
+++ b/core/modules/layout_builder/layout_builder.install
@@ -6,6 +6,8 @@
  */
 
 use Drupal\Core\Cache\Cache;
+use Drupal\Core\Database\Database;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
 use Drupal\layout_builder\Section;
 
@@ -38,3 +40,75 @@ function layout_builder_install() {
   // prepare for future changes.
   Cache::invalidateTags(['rendered']);
 }
+
+/**
+ * Implements hook_schema().
+ */
+function layout_builder_schema() {
+  $schema['inline_block_content_usage'] = [
+    'description' => 'Track where a block_content entity is used.',
+    'fields' => [
+      'block_content_id' => [
+        'description' => 'The block_content entity ID.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ],
+      'layout_entity_type' => [
+        'description' => 'The entity type of the parent entity.',
+        'type' => 'varchar_ascii',
+        'length' => EntityTypeInterface::ID_MAX_LENGTH,
+        'not null' => FALSE,
+        'default' => '',
+      ],
+      'layout_entity_id' => [
+        'description' => 'The ID of the parent entity.',
+        'type' => 'varchar_ascii',
+        'length' => 128,
+        'not null' => FALSE,
+        'default' => 0,
+      ],
+    ],
+    'primary key' => ['block_content_id'],
+    'indexes' => [
+      'type_id' => ['layout_entity_type', 'layout_entity_id'],
+    ],
+  ];
+  return $schema;
+}
+
+/**
+ * Create the 'inline_block_content_usage' table.
+ */
+function layout_builder_update_8001() {
+  $inline_block_content_usage = [
+    'description' => 'Track where a entity is used.',
+    'fields' => [
+      'block_content_id' => [
+        'description' => 'The block_content entity ID.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ],
+      'layout_entity_type' => [
+        'description' => 'The entity type of the parent entity.',
+        'type' => 'varchar_ascii',
+        'length' => EntityTypeInterface::ID_MAX_LENGTH,
+        'not null' => FALSE,
+        'default' => '',
+      ],
+      'layout_entity_id' => [
+        'description' => 'The ID of the parent entity.',
+        'type' => 'varchar_ascii',
+        'length' => 128,
+        'not null' => FALSE,
+        'default' => 0,
+      ],
+    ],
+    'primary key' => ['block_content_id'],
+    'indexes' => [
+      'type_id' => ['layout_entity_type', 'layout_entity_id'],
+    ],
+  ];
+  Database::getConnection()->schema()->createTable('inline_block_content_usage', $inline_block_content_usage);
+}
diff --git a/core/modules/layout_builder/layout_builder.module b/core/modules/layout_builder/layout_builder.module
index 2138a045f3..c913c7e4a1 100644
--- a/core/modules/layout_builder/layout_builder.module
+++ b/core/modules/layout_builder/layout_builder.module
@@ -5,6 +5,7 @@
  * Provides hook implementations for Layout Builder.
  */
 
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
@@ -12,6 +13,7 @@
 use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
 use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplayStorage;
 use Drupal\layout_builder\Form\LayoutBuilderEntityViewDisplayForm;
+use Drupal\layout_builder\EntityOperations;
 
 /**
  * Implements hook_help().
@@ -81,3 +83,51 @@ function layout_builder_field_config_delete(FieldConfigInterface $field_config)
   $sample_entity_generator->delete($field_config->getTargetEntityTypeId(), $field_config->getTargetBundle());
   \Drupal::service('plugin.manager.block')->clearCachedDefinitions();
 }
+
+/**
+ * Implements hook_entity_presave().
+ */
+function layout_builder_entity_presave(EntityInterface $entity) {
+  if (\Drupal::moduleHandler()->moduleExists('block_content')) {
+    /** @var \Drupal\layout_builder\EntityOperations $entity_operations */
+    $entity_operations = \Drupal::classResolver(EntityOperations::class);
+    $entity_operations->handlePreSave($entity);
+  }
+}
+
+/**
+ * Implements hook_entity_delete().
+ */
+function layout_builder_entity_delete(EntityInterface $entity) {
+  if (\Drupal::moduleHandler()->moduleExists('block_content')) {
+    /** @var \Drupal\layout_builder\EntityOperations $entity_operations */
+    $entity_operations = \Drupal::classResolver(EntityOperations::class);
+    $entity_operations->handleEntityDelete($entity);
+  }
+}
+
+/**
+ * Implements hook_cron().
+ */
+function layout_builder_cron() {
+  if (\Drupal::moduleHandler()->moduleExists('block_content')) {
+    /** @var \Drupal\layout_builder\EntityOperations $entity_operations */
+    $entity_operations = \Drupal::classResolver(EntityOperations::class);
+    $entity_operations->removeUnused();
+  }
+}
+
+/**
+ * Implements hook_plugin_filter_TYPE_alter().
+ */
+function layout_builder_plugin_filter_block_alter(array &$definitions, array $extra, $consumer) {
+  // @todo Determine the 'inline_block_content' blocks should be allowe outside
+  //   of layout_builder https://www.drupal.org/node/2979142.
+  if ($consumer !== 'layout_builder') {
+    foreach ($definitions as $id => $definition) {
+      if ($definition['id'] === 'inline_block_content') {
+        unset($definitions[$id]);
+      }
+    }
+  }
+}
diff --git a/core/modules/layout_builder/layout_builder.services.yml b/core/modules/layout_builder/layout_builder.services.yml
index 4fe50929bd..1f3a70e44e 100644
--- a/core/modules/layout_builder/layout_builder.services.yml
+++ b/core/modules/layout_builder/layout_builder.services.yml
@@ -39,3 +39,6 @@ services:
   logger.channel.layout_builder:
     parent: logger.channel_base
     arguments: ['layout_builder']
+  inline_block_content.usage:
+    class: Drupal\layout_builder\InlineBlockContentUsage
+    arguments: ['@database']
diff --git a/core/modules/layout_builder/src/EntityOperations.php b/core/modules/layout_builder/src/EntityOperations.php
new file mode 100644
index 0000000000..a04e62ca02
--- /dev/null
+++ b/core/modules/layout_builder/src/EntityOperations.php
@@ -0,0 +1,331 @@
+<?php
+
+namespace Drupal\layout_builder;
+
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Entity\RevisionableInterface;
+use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
+use Drupal\layout_builder\Plugin\Block\InlineBlockContentBlock;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines a class for reacting to entity events.
+ *
+ * @internal
+ */
+class EntityOperations implements ContainerInjectionInterface {
+
+  /**
+   * Inline block content usage tracking service.
+   *
+   * @var \Drupal\layout_builder\InlineBlockContentUsage
+   */
+  protected $usage;
+
+  /**
+   * The block storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $storage;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a new  EntityOperations object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   The entity type manager service.
+   * @param \Drupal\layout_builder\InlineBlockContentUsage $usage
+   *   Inline block content usage tracking service.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   */
+  public function __construct(EntityTypeManagerInterface $entityTypeManager, InlineBlockContentUsage $usage) {
+    $this->entityTypeManager = $entityTypeManager;
+    $this->storage = $entityTypeManager->getStorage('block_content');
+    $this->usage = $usage;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity_type.manager'),
+      $container->get('inline_block_content.usage')
+    );
+  }
+
+  /**
+   * Remove all unused entities on save.
+   *
+   * Entities that were used in prevision revisions will be removed if not
+   * saving a new revision.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The parent entity.
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   */
+  protected function removeUnusedForEntityOnSave(EntityInterface $entity) {
+    // If the entity is new or '$entity->original' is not set then there will
+    // not be any unused inline blocks to remove.
+    if ($entity->isNew() || !isset($entity->original)) {
+      return;
+    }
+    $sections = $this->getEntitySections($entity);
+    // If this is a layout override and there are no sections then it is a new
+    // override.
+    if ($entity instanceof FieldableEntityInterface && $entity->hasField('layout_builder__layout') && empty($sections)) {
+      return;
+    }
+    // If this is a revisionable entity then do not remove block_content
+    // entities. They could be referenced in previous revisions even if this is
+    // not a new revision.
+    if ($entity instanceof RevisionableInterface) {
+      return;
+    }
+    $original_sections = $this->getEntitySections($entity->original);
+    $current_revision_ids = $this->getInBlockRevisionIdsInSection($sections);
+    // If there are any revisions in the original that aren't current there may
+    // some blocks that need to be removed.
+    if ($original_revision_ids = array_diff($this->getInBlockRevisionIdsInSection($original_sections), $current_revision_ids)) {
+      if ($removed_ids = array_diff($this->getBlockIdsForRevisionIds($original_revision_ids), $this->getBlockIdsForRevisionIds($current_revision_ids))) {
+        foreach ($removed_ids as $block_content_id) {
+          if ($block = $this->storage->load($block_content_id)) {
+            $block->delete();
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Handles entity tracking on deleting a parent entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The parent entity.
+   */
+  public function handleEntityDelete(EntityInterface $entity) {
+    if ($this->isLayoutCompatibleEntity($entity)) {
+      $this->usage->removeByLayoutEntity($entity);
+    }
+  }
+
+  /**
+   * Gets the sections for an entity if any.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   *
+   * @return \Drupal\layout_builder\Section[]|null
+   *   The entity layout sections if available.
+   *
+   * @internal
+   */
+  protected function getEntitySections(EntityInterface $entity) {
+    if ($entity->getEntityTypeId() === 'entity_view_display' && $entity instanceof LayoutBuilderEntityViewDisplay) {
+      return $entity->getSections();
+    }
+    elseif ($entity instanceof FieldableEntityInterface && $entity->hasField('layout_builder__layout')) {
+      return $entity->get('layout_builder__layout')->getSections();
+    }
+    return NULL;
+  }
+
+  /**
+   * Handles saving a parent entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The parent entity.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   * @throws \Exception
+   */
+  public function handlePreSave(EntityInterface $entity) {
+    if (!$this->isLayoutCompatibleEntity($entity)) {
+      return;
+    }
+    $duplicate_blocks = FALSE;
+
+    if ($sections = $this->getEntitySections($entity)) {
+      if ($entity instanceof FieldableEntityInterface && $entity->hasField('layout_builder__layout')) {
+        if (!$entity->isNew() && isset($entity->original)) {
+          /** @var \Drupal\layout_builder\Field\LayoutSectionItemList $original_sections_field */
+          $original_sections_field = $entity->original->get('layout_builder__layout');
+          if ($original_sections_field->isEmpty()) {
+            // @todo Is there a better way to tell if Layout Override is new?
+            // what if is overridden and all sections removed. Currently if you
+            // remove all sections from an override it reverts to the default.
+            // Is that a feature or a bug?
+            $duplicate_blocks = TRUE;
+          }
+        }
+      }
+      $new_revision = FALSE;
+      if ($entity instanceof RevisionableInterface) {
+        // If the parent entity will have a new revision create a new revision
+        // of the block.
+        // @todo Currently revisions are not actually created.
+        // @see https://www.drupal.org/node/2937199
+        // To bypass this always make a revision because the parent entity is
+        // instance of RevisionableInterface. After the issue is fixed only
+        // create a new revision if '$entity->isNewRevision()'.
+        $new_revision = TRUE;
+      }
+
+      foreach ($this->getInlineBlockComponents($sections) as $component) {
+        /** @var \Drupal\layout_builder\Plugin\Block\InlineBlockContentBlock $plugin */
+        $plugin = $component->getPlugin();
+        $pre_save_configuration = $plugin->getConfiguration();
+        $plugin->saveBlockContent($new_revision, $duplicate_blocks);
+        $post_save_configuration = $plugin->getConfiguration();
+        if ($duplicate_blocks || (empty($pre_save_configuration['block_revision_id']) && !empty($post_save_configuration['block_revision_id']))) {
+          $this->usage->addUsage($this->getPluginBlockId($plugin), $entity->getEntityTypeId(), $entity->id());
+        }
+        $component->setConfiguration($plugin->getConfiguration());
+      }
+    }
+    $this->removeUnusedForEntityOnSave($entity);
+  }
+
+  /**
+   * Gets components that have Inline Block plugins.
+   *
+   * @param \Drupal\layout_builder\Section[] $sections
+   *   The layout sections.
+   *
+   * @return \Drupal\layout_builder\SectionComponent[]
+   *   The components that contain Inline Block plugins.
+   */
+  protected function getInlineBlockComponents(array $sections) {
+    $inline_components = [];
+    foreach ($sections as $section) {
+      $components = $section->getComponents();
+
+      foreach ($components as $component) {
+        $plugin = $component->getPlugin();
+        if ($plugin instanceof InlineBlockContentBlock) {
+          $inline_components[] = $component;
+        }
+      }
+    }
+    return $inline_components;
+  }
+
+  /**
+   * Determines if an entity can have a layout.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to check.
+   *
+   * @return bool
+   *   TRUE if the entity can have a layout otherwise FALSE.
+   */
+  protected function isLayoutCompatibleEntity(EntityInterface $entity) {
+    return ($entity->getEntityTypeId() === 'entity_view_display' && $entity instanceof LayoutBuilderEntityViewDisplay) ||
+      ($entity instanceof FieldableEntityInterface && $entity->hasField('layout_builder__layout'));
+  }
+
+  /**
+   * Gets a block ID for a inline block content plugin.
+   *
+   * @param \Drupal\Component\Plugin\PluginInspectionInterface $plugin
+   *   The inline block content plugin.
+   *
+   * @return int
+   *   The block content ID or null none available.
+   */
+  protected function getPluginBlockId(PluginInspectionInterface $plugin) {
+    /** @var \Drupal\Component\Plugin\ConfigurablePluginInterface $plugin */
+    $configuration = $plugin->getConfiguration();
+    if (!empty($configuration['block_revision_id'])) {
+      $query = $this->storage->getQuery();
+      $query->condition('revision_id', $configuration['block_revision_id']);
+      return array_values($query->execute())[0];
+    }
+    return NULL;
+  }
+
+  /**
+   * Delete the content blocks and delete the usage records.
+   *
+   * @param int[] $block_content_ids
+   *   The block content entity IDs.
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   */
+  protected function deleteBlocksAndUsage(array $block_content_ids) {
+    foreach ($block_content_ids as $block_content_id) {
+      if ($block = $this->storage->load($block_content_id)) {
+        $block->delete();
+      }
+    }
+    $this->usage->deleteUsage($block_content_ids);
+  }
+
+  /**
+   * Removes unused block content entities.
+   *
+   * @param int $limit
+   *   The maximum number of block content entities to remove.
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   */
+  public function removeUnused($limit = 100) {
+    $this->deleteBlocksAndUsage($this->usage->getUnused($limit));
+  }
+
+  /**
+   * Gets revision IDs for layout sections.
+   *
+   * @param \Drupal\layout_builder\Section[] $sections
+   *   The layout sections.
+   *
+   * @return int[]
+   *   The revision IDs.
+   */
+  protected function getInBlockRevisionIdsInSection(array $sections) {
+    $revision_ids = [];
+    foreach ($this->getInlineBlockComponents($sections) as $component) {
+      $configuration = $component->getPlugin()->getConfiguration();
+      if (!empty($configuration['block_revision_id'])) {
+        $revision_ids[] = $configuration['block_revision_id'];
+      }
+    }
+    return $revision_ids;
+  }
+
+  /**
+   * Gets blocks IDs for an array of revision IDs.
+   *
+   * @param int[] $revision_ids
+   *   The revision IDs.
+   *
+   * @return int[]
+   *   The block IDs.
+   */
+  protected function getBlockIdsForRevisionIds(array $revision_ids) {
+    if ($revision_ids) {
+      $query = $this->storage->getQuery();
+      $query->condition('revision_id', $revision_ids, 'IN');
+      $block_ids = $query->execute();
+      return $block_ids;
+    }
+    return [];
+  }
+
+}
diff --git a/core/modules/layout_builder/src/EventSubscriber/BlockComponentRenderArray.php b/core/modules/layout_builder/src/EventSubscriber/BlockComponentRenderArray.php
index 181ed8229b..fc3818bf31 100644
--- a/core/modules/layout_builder/src/EventSubscriber/BlockComponentRenderArray.php
+++ b/core/modules/layout_builder/src/EventSubscriber/BlockComponentRenderArray.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\layout_builder\EventSubscriber;
 
+use Drupal\Core\Access\AccessDependentInterface;
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Block\BlockPluginInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -56,6 +57,18 @@ public function onBuildRender(SectionComponentBuildRenderArrayEvent $event) {
       return;
     }
 
+    // Set block access dependency even if we are not checking access on
+    // this level. The block itself may render another AccessDependentInterface
+    // object and need to pass on this value.
+    if ($block instanceof AccessDependentInterface) {
+      $contexts = $event->getContexts();
+      if (isset($contexts['layout_builder.entity'])) {
+        if ($entity = $contexts['layout_builder.entity']->getContextValue()) {
+          $block->setAccessDependency($entity);
+        }
+      }
+    }
+
     // Only check access if the component is not being previewed.
     if ($event->inPreview()) {
       $access = AccessResult::allowed()->setCacheMaxAge(0);
diff --git a/core/modules/layout_builder/src/Form/RevertOverridesForm.php b/core/modules/layout_builder/src/Form/RevertOverridesForm.php
index b6d07d9089..cf7d91844d 100644
--- a/core/modules/layout_builder/src/Form/RevertOverridesForm.php
+++ b/core/modules/layout_builder/src/Form/RevertOverridesForm.php
@@ -103,6 +103,9 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
+    // @todo Remove this quick fix after https://www.drupal.org/node/2970801
+    $this->sectionStorage = \Drupal::service('plugin.manager.layout_builder.section_storage')->loadFromStorageId($this->sectionStorage->getStorageType(), $this->sectionStorage->getStorageId());
+
     // Remove all sections.
     while ($this->sectionStorage->count()) {
       $this->sectionStorage->removeSection(0);
diff --git a/core/modules/layout_builder/src/InlineBlockContentUsage.php b/core/modules/layout_builder/src/InlineBlockContentUsage.php
new file mode 100644
index 0000000000..9166a79c0d
--- /dev/null
+++ b/core/modules/layout_builder/src/InlineBlockContentUsage.php
@@ -0,0 +1,96 @@
+<?php
+
+namespace Drupal\layout_builder;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Service class to track non-reusable Blocks entities usage.
+ */
+class InlineBlockContentUsage {
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * Creates an InlineBlockContentUsage object.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection.
+   */
+  public function __construct(Connection $connection) {
+    $this->connection = $connection;
+  }
+
+  /**
+   * Add a usage record.
+   *
+   * @param int $block_content_id
+   *   The block content id.
+   * @param string $layout_entity_type
+   *   The layout entity type.
+   * @param string $layout_entity_id
+   *   The layout entity id.
+   *
+   * @throws \Exception
+   */
+  public function addUsage($block_content_id, $layout_entity_type, $layout_entity_id) {
+    $this->connection->merge('inline_block_content_usage')
+      ->keys([
+        'block_content_id' => $block_content_id,
+        'layout_entity_id' => $layout_entity_id,
+        'layout_entity_type' => $layout_entity_type,
+      ])->execute();
+  }
+
+  /**
+   * Gets unused inline block content IDs.
+   *
+   * @param int $limit
+   *   The maximum number of block content entity IDs to return.
+   *
+   * @return int[]
+   *   The entity IDs.
+   */
+  public function getUnused($limit = 100) {
+    $query = $this->connection->select('inline_block_content_usage', 't');
+    $query->fields('t', ['block_content_id']);
+    $query->isNull('layout_entity_id');
+    $query->isNull('layout_entity_type');
+    return $query->range(0, $limit)->execute()->fetchCol();
+  }
+
+  /**
+   * Remove usage record by layout entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The layout entity.
+   */
+  public function removeByLayoutEntity(EntityInterface $entity) {
+    $query = $this->connection->update('inline_block_content_usage')
+      ->fields([
+        'layout_entity_type' => NULL,
+        'layout_entity_id' => NULL,
+      ]);
+    $query->condition('layout_entity_type', $entity->getEntityTypeId());
+    $query->condition('layout_entity_id', $entity->id());
+    $query->execute();
+  }
+
+  /**
+   * Delete the content blocks and delete the usage records.
+   *
+   * @param int[] $block_content_ids
+   *   The block content entity IDs.
+   */
+  public function deleteUsage(array $block_content_ids) {
+    $query = $this->connection->delete('inline_block_content_usage')->condition('block_content_id', $block_content_ids, 'IN');
+    $query->execute();
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Plugin/Block/InlineBlockContentBlock.php b/core/modules/layout_builder/src/Plugin/Block/InlineBlockContentBlock.php
new file mode 100644
index 0000000000..9ab2d26ec3
--- /dev/null
+++ b/core/modules/layout_builder/src/Plugin/Block/InlineBlockContentBlock.php
@@ -0,0 +1,287 @@
+<?php
+
+namespace Drupal\layout_builder\Plugin\Block;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Access\AccessDependentInterface;
+use Drupal\Core\Access\AccessDependentTrait;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Block\BlockBase;
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Form\SubformStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines an inline custom block plugin type.
+ *
+ * @Block(
+ *  id = "inline_block_content",
+ *  admin_label = @Translation("Inline custom block"),
+ *  category = @Translation("Inline custom blocks"),
+ *  deriver = "Drupal\layout_builder\Plugin\Derivative\InlineBlockContentDeriver",
+ * )
+ */
+class InlineBlockContentBlock extends BlockBase implements ContainerFactoryPluginInterface, AccessDependentInterface {
+
+  use AccessDependentTrait;
+
+  /**
+   * The entity type manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The block content entity.
+   *
+   * @var \Drupal\block_content\BlockContentInterface
+   */
+  protected $blockContent;
+
+  /**
+   * The entity display repository.
+   *
+   * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
+   */
+  protected $entityDisplayRepository;
+
+  /**
+   * Whether a new serialized block is being created.
+   *
+   * @var bool
+   */
+  protected $isNew = TRUE;
+
+  /**
+   * Constructs a new InlineBlockContentBlock.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin ID for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager service.
+   * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
+   *   The entity display repository.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityDisplayRepositoryInterface $entity_display_repository) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $this->entityTypeManager = $entity_type_manager;
+    $this->entityDisplayRepository = $entity_display_repository;
+    if (!empty($this->configuration['block_revision_id']) || !empty($this->configuration['block_serialized'])) {
+      $this->isNew = FALSE;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('entity_type.manager'),
+      $container->get('entity_display.repository')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return [
+      'view_mode' => 'full',
+      'block_revision_id' => NULL,
+      'block_serialized' => NULL,
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function blockForm($form, FormStateInterface $form_state) {
+    $block = $this->getEntity();
+
+    // Add the entity form display in a process callback so that #parents can
+    // be successfully propagated to field widgets.
+    $form['block_form'] = [
+      '#type' => 'container',
+      '#process' => [[static::class, 'processBlockForm']],
+      '#block' => $block,
+    ];
+
+    $options = $this->entityDisplayRepository->getViewModeOptionsByBundle('block_content', $block->bundle());
+
+    $form['view_mode'] = [
+      '#type' => 'select',
+      '#options' => $options,
+      '#title' => $this->t('View mode'),
+      '#description' => $this->t('The view mode in which to render the block.'),
+      '#default_value' => $this->configuration['view_mode'],
+      '#access' => count($options) > 1,
+    ];
+    return $form;
+  }
+
+  /**
+   * Process callback to insert a Custom Block form.
+   *
+   * @param array $element
+   *   The containing element.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return array
+   *   The containing element, with the Custom Block form inserted.
+   */
+  public static function processBlockForm(array $element, FormStateInterface $form_state) {
+    /** @var \Drupal\block_content\BlockContentInterface $block */
+    $block = $element['#block'];
+    EntityFormDisplay::collectRenderDisplay($block, 'edit')->buildForm($block, $element, $form_state);
+    $element['revision_log']['#access'] = FALSE;
+    $element['info']['#access'] = FALSE;
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function blockValidate($form, FormStateInterface $form_state) {
+    $block_form = $form['block_form'];
+    /** @var \Drupal\block_content\BlockContentInterface $block */
+    $block = $block_form['#block'];
+    $form_display = EntityFormDisplay::collectRenderDisplay($block, 'edit');
+    $complete_form_state = $form_state instanceof SubformStateInterface ? $form_state->getCompleteFormState() : $form_state;
+    $form_display->extractFormValues($block, $block_form, $complete_form_state);
+    $form_display->validateFormValues($block, $block_form, $complete_form_state);
+    // @todo Remove when https://www.drupal.org/project/drupal/issues/2948549 is closed.
+    $form_state->setTemporaryValue('block_form_parents', $block_form['#parents']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function blockSubmit($form, FormStateInterface $form_state) {
+    $this->configuration['view_mode'] = $form_state->getValue('view_mode');
+
+    // @todo Remove when https://www.drupal.org/project/drupal/issues/2948549 is closed.
+    $block_form = NestedArray::getValue($form, $form_state->getTemporaryValue('block_form_parents'));
+    /** @var \Drupal\block_content\BlockContentInterface $block */
+    $block = $block_form['#block'];
+    $form_display = EntityFormDisplay::collectRenderDisplay($block, 'edit');
+    $complete_form_state = ($form_state instanceof SubformStateInterface) ? $form_state->getCompleteFormState() : $form_state;
+    $form_display->extractFormValues($block, $block_form, $complete_form_state);
+    $block->setInfo($this->configuration['label']);
+    $this->configuration['block_serialized'] = serialize($block);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function blockAccess(AccountInterface $account) {
+    if ($this->getEntity()) {
+      return $this->getEntity()->access('view', $account, TRUE);
+    }
+    return AccessResult::forbidden();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    $block = $this->getEntity();
+    return $this->entityTypeManager->getViewBuilder($block->getEntityTypeId())->view($block, $this->configuration['view_mode']);
+  }
+
+  /**
+   * Loads or creates the block content entity of the block.
+   *
+   * @return \Drupal\block_content\BlockContentInterface
+   *   The block content entity.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   */
+  protected function getEntity() {
+    if (!isset($this->blockContent)) {
+      if (!empty($this->configuration['block_serialized'])) {
+        $this->blockContent = unserialize($this->configuration['block_serialized']);
+      }
+      elseif (!empty($this->configuration['block_revision_id'])) {
+        $entity = $this->entityTypeManager->getStorage('block_content')->loadRevision($this->configuration['block_revision_id']);
+        $this->blockContent = $entity;
+      }
+      else {
+        $this->blockContent = $this->entityTypeManager->getStorage('block_content')->create([
+          'type' => $this->getDerivativeId(),
+          'reusable' => FALSE,
+        ]);
+      }
+    }
+    if ($this->blockContent instanceof AccessDependentInterface && $dependee = $this->getAccessDependency()) {
+      $this->blockContent->setAccessDependency($dependee);
+    }
+    return $this->blockContent;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $form = parent::buildConfigurationForm($form, $form_state);
+    if ($this->isNew) {
+      // If the Content Block is new then don't provide a default label.
+      // @todo Blocks may be serialized before the layout is saved so we
+      // can't check $this->getEntity()->isNew().
+      unset($form['label']['#default_value']);
+    }
+    $form['label']['#description'] = $this->t('The title of the block as shown to the user.');
+    return $form;
+  }
+
+  /**
+   * Saves the "block_content" entity for this plugin.
+   *
+   * @param bool $new_revision
+   *   Whether to create new revision.
+   * @param bool $duplicate_block
+   *   Whether to duplicate the "block_content" entity.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   */
+  public function saveBlockContent($new_revision = FALSE, $duplicate_block = FALSE) {
+    /** @var \Drupal\block_content\BlockContentInterface $block */
+    $block = NULL;
+    if ($duplicate_block && !empty($this->configuration['block_revision_id']) && empty($this->configuration['block_serialized'])) {
+      $entity = $this->entityTypeManager->getStorage('block_content')->loadRevision($this->configuration['block_revision_id']);
+      $block = $entity->createDuplicate();
+    }
+    elseif (isset($this->configuration['block_serialized'])) {
+      $block = unserialize($this->configuration['block_serialized']);
+      if (!empty($this->configuration['block_revision_id']) && $duplicate_block) {
+        $block = $block->createDuplicate();
+      }
+    }
+    if ($block) {
+      if ($new_revision) {
+        $block->setNewRevision();
+      }
+      $block->save();
+      $this->configuration['block_revision_id'] = $block->getRevisionId();
+      $this->configuration['block_serialized'] = NULL;
+    }
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Plugin/Derivative/InlineBlockContentDeriver.php b/core/modules/layout_builder/src/Plugin/Derivative/InlineBlockContentDeriver.php
new file mode 100644
index 0000000000..07df40e863
--- /dev/null
+++ b/core/modules/layout_builder/src/Plugin/Derivative/InlineBlockContentDeriver.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Drupal\layout_builder\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Retrieves block plugin definitions for all custom block types.
+ */
+class InlineBlockContentDeriver extends DeriverBase implements ContainerDeriverInterface {
+
+  /**
+   * The entity type manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a BlockContentDeriver object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    return new static(
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    $this->derivatives = [];
+    if ($this->entityTypeManager->hasDefinition('block_content_type')) {
+      $block_content_types = $this->entityTypeManager->getStorage('block_content_type')->loadMultiple();
+      foreach ($block_content_types as $id => $type) {
+        $this->derivatives[$id] = $base_plugin_definition;
+        $this->derivatives[$id]['admin_label'] = $type->label();
+        $this->derivatives[$id]['config_dependencies'][$type->getConfigDependencyKey()][] = $type->getConfigDependencyName();
+      }
+    }
+    return parent::getDerivativeDefinitions($base_plugin_definition);
+  }
+
+}
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
index 40c0503cea..4fbf86f56f 100644
--- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
@@ -101,6 +101,7 @@ public function testLayoutBuilderUi() {
     // Save the defaults.
     $assert_session->linkExists('Save Layout');
     $this->clickLink('Save Layout');
+    $assert_session->pageTextContains('The layout has been saved.');
     $assert_session->addressEquals("$field_ui_prefix/display/default");
 
     // The node uses the defaults, no overrides available.
diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockContentBlockTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockContentBlockTest.php
new file mode 100644
index 0000000000..c65bac8b78
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockContentBlockTest.php
@@ -0,0 +1,536 @@
+<?php
+
+namespace Drupal\Tests\layout_builder\FunctionalJavascript;
+
+use Drupal\block_content\Entity\BlockContentType;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+use Drupal\node\Entity\Node;
+use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait;
+
+/**
+ * Tests that the inline block feature works correctly.
+ *
+ * @group layout_builder
+ */
+class InlineBlockContentBlockTest extends JavascriptTestBase {
+
+  use ContextualLinkClickTrait;
+
+  /**
+   * Locator for inline blocks.
+   */
+  const INLINE_BLOCK_LOCATOR = '.block-inline-block-contentbasic';
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'block_content',
+    'layout_builder',
+    'block',
+    'node',
+    'contextual',
+  ];
+
+  /**
+   * The block storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $blockStorage;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    // @todo The Layout Builder UI relies on local tasks; fix in
+    //   https://www.drupal.org/project/drupal/issues/2917777.
+    $this->drupalPlaceBlock('local_tasks_block');
+
+    $this->createContentType(['type' => 'bundle_with_section_field', 'new_revision' => TRUE]);
+    $this->createNode([
+      'type' => 'bundle_with_section_field',
+      'title' => 'The node title',
+      'body' => [
+        [
+          'value' => 'The node body',
+        ],
+      ],
+    ]);
+    $this->createNode([
+      'type' => 'bundle_with_section_field',
+      'title' => 'The node2 title',
+      'body' => [
+        [
+          'value' => 'The node2 body',
+        ],
+      ],
+    ]);
+    $bundle = BlockContentType::create([
+      'id' => 'basic',
+      'label' => 'Basic block',
+      'revision' => 1,
+    ]);
+    $bundle->save();
+    block_content_add_body_field($bundle->id());
+
+    $this->blockStorage = $this->container->get('entity_type.manager')->getStorage('block_content');
+  }
+
+  /**
+   * Tests adding and editing of inline blocks.
+   */
+  public function testInlineBlocks() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'access contextual links',
+      'configure any layout',
+      'administer node display',
+      'administer node fields',
+    ]));
+
+    $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
+    $this->drupalGet($field_ui_prefix . '/display/default');
+    $this->clickLink('Manage layout');
+    $assert_session->addressEquals("$field_ui_prefix/display-layout/default");
+    // Add a basic block with the body field set.
+    $this->addInlineBlockToLayout('Block title', 'The DEFAULT block body');
+    $this->assertSaveLayout();
+
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('The DEFAULT block body');
+    $this->drupalGet('node/2');
+    $assert_session->pageTextContains('The DEFAULT block body');
+
+    // Enable overrides.
+    $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
+    $this->drupalGet('node/1/layout');
+
+    // Confirm the block can be edited.
+    $this->drupalGet('node/1/layout');
+    $this->configureInlineBlock('The DEFAULT block body', 'The NEW block body!');
+    $this->assertSaveLayout();
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('The NEW block body');
+    $assert_session->pageTextNotContains('The DEFAULT block body');
+    $this->drupalGet('node/2');
+    // Node 2 should use default layout.
+    $assert_session->pageTextContains('The DEFAULT block body');
+    $assert_session->pageTextNotContains('The NEW block body');
+
+    // Add a basic block with the body field set.
+    $this->drupalGet('node/1/layout');
+    $this->addInlineBlockToLayout('2nd Block title', 'The 2nd block body');
+    $this->assertSaveLayout();
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('The NEW block body!');
+    $assert_session->pageTextContains('The 2nd block body');
+    $this->drupalGet('node/2');
+    // Node 2 should use default layout.
+    $assert_session->pageTextContains('The DEFAULT block body');
+    $assert_session->pageTextNotContains('The NEW block body');
+    $assert_session->pageTextNotContains('The 2nd block body');
+
+    // Confirm the block can be edited.
+    $this->drupalGet('node/1/layout');
+    /* @var \Behat\Mink\Element\NodeElement $inline_block_2 */
+    $inline_block_2 = $page->findAll('css', static::INLINE_BLOCK_LOCATOR)[1];
+    $uuid = $inline_block_2->getAttribute('data-layout-block-uuid');
+    $block_css_locator = static::INLINE_BLOCK_LOCATOR . "[data-layout-block-uuid=\"$uuid\"]";
+    $this->configureInlineBlock('The 2nd block body', 'The 2nd NEW block body!', $block_css_locator);
+    $this->assertSaveLayout();
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('The NEW block body!');
+    $assert_session->pageTextContains('The 2nd NEW block body!');
+    $this->drupalGet('node/2');
+    // Node 2 should use default layout.
+    $assert_session->pageTextContains('The DEFAULT block body');
+    $assert_session->pageTextNotContains('The NEW block body!');
+    $assert_session->pageTextNotContains('The 2nd NEW block body!');
+
+    // The default layout entity block should be changed.
+    $this->drupalGet("$field_ui_prefix/display-layout/default");
+    $assert_session->pageTextContains('The DEFAULT block body');
+    // Confirm default layout still only has 1 entity block.
+    $assert_session->elementsCount('css', static::INLINE_BLOCK_LOCATOR, 1);
+  }
+
+  /**
+   * Tests adding a new entity block and then not saving the layout.
+   *
+   * @dataProvider layoutNoSaveProvider
+   */
+  public function testNoLayoutSave($operation, $no_save_link_text, $confirm_button_text) {
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'access contextual links',
+      'configure any layout',
+      'administer node display',
+    ]));
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+    $this->assertEmpty($this->blockStorage->loadMultiple(), 'No entity blocks exist');
+    $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
+    // Enable overrides.
+    $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
+
+    $this->drupalGet('node/1/layout');
+    $this->addInlineBlockToLayout('Block title', 'The block body');
+    $this->clickLink($no_save_link_text);
+    if ($confirm_button_text) {
+      $page->pressButton($confirm_button_text);
+    }
+    $this->drupalGet('node/1');
+    $this->assertEmpty($this->blockStorage->loadMultiple(), 'No entity blocks were created when layout is canceled.');
+    $assert_session->pageTextNotContains('The block body');
+
+    $this->drupalGet('node/1/layout');
+
+    $this->addInlineBlockToLayout('Block title', 'The block body');
+    $this->assertSaveLayout();
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('The block body');
+    $blocks = $this->blockStorage->loadMultiple();
+    $this->assertEquals(count($blocks), 1);
+    /* @var \Drupal\Core\Entity\ContentEntityBase $block */
+    $block = array_pop($blocks);
+    $revision_id = $block->getRevisionId();
+
+    // Confirm the block can be edited.
+    $this->drupalGet('node/1/layout');
+    $this->configureInlineBlock('The block body', 'The block updated body');
+    $assert_session->pageTextContains('The block updated body');
+
+    $this->clickLink($no_save_link_text);
+    if ($confirm_button_text) {
+      $page->pressButton($confirm_button_text);
+    }
+    $this->drupalGet('node/1');
+
+    $blocks = $this->blockStorage->loadMultiple();
+    // When reverting or canceling the update block should not be on the page.
+    $assert_session->pageTextNotContains('The block updated body');
+    if ($operation === 'cancel') {
+      // When canceling the original block body should appear.
+      $assert_session->pageTextContains('The block body');
+
+      $this->assertEquals(count($blocks), 1);
+      $block = array_pop($blocks);
+      $this->assertEquals($block->getRevisionId(), $revision_id);
+      $this->assertEquals($block->get('body')->getValue()[0]['value'], 'The block body');
+    }
+    else {
+      // The block should not be visible.
+      // Blocks are currently only deleted when the parent entity is deleted.
+      $assert_session->pageTextNotContains('The block body');
+    }
+  }
+
+  /**
+   * Provides test data for ::testNoLayoutSave().
+   */
+  public function layoutNoSaveProvider() {
+    return [
+      'cancel' => [
+        'cancel',
+        'Cancel Layout',
+        NULL,
+      ],
+      'revert' => [
+        'revert',
+        'Revert to defaults',
+        'Revert',
+      ],
+    ];
+  }
+
+  /**
+   * Saves a layout and asserts the message is correct.
+   *
+   * @throws \Behat\Mink\Exception\ExpectationException
+   * @throws \Behat\Mink\Exception\ResponseTextException
+   */
+  protected function assertSaveLayout() {
+    $assert_session = $this->assertSession();
+    $assert_session->linkExists('Save Layout');
+    $this->clickLink('Save Layout');
+    $this->assertNotEmpty($assert_session->waitForElement('css', '.messages--status'));
+    if (stristr($this->getUrl(), 'admin/structure') === FALSE) {
+      $assert_session->pageTextContains('The layout override has been saved.');
+    }
+    else {
+      $assert_session->pageTextContains('The layout has been saved.');
+    }
+  }
+
+  /**
+   * Tests entity blocks revisioning.
+   */
+  public function testInlineBlocksRevisioning() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'access contextual links',
+      'configure any layout',
+      'administer node display',
+      'administer node fields',
+      'administer nodes',
+      'bypass node access',
+    ]));
+
+    // Enable override.
+    $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
+    $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
+    $this->drupalGet('node/1/layout');
+
+    // Add a entity block.
+    $this->addInlineBlockToLayout('Block title', 'The DEFAULT block body');
+    $this->assertSaveLayout();
+    $this->drupalGet('node/1');
+
+    $assert_session->pageTextContains('The DEFAULT block body');
+
+    /** @var \Drupal\node\NodeStorageInterface $node_storage */
+    $node_storage = $this->container->get('entity_type.manager')->getStorage('node');
+    $original_revision_id = $node_storage->getLatestRevisionId(1);
+
+    // Create a new revision.
+    $this->drupalGet('node/1/edit');
+    $page->findField('title[0][value]')->setValue('Node updated');
+    $page->pressButton('Save');
+
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('The DEFAULT block body');
+
+    $assert_session->linkExists('Revisions');
+
+    // Update the block.
+    $this->drupalGet('node/1/layout');
+    $this->configureInlineBlock('The DEFAULT block body', 'The NEW block body');
+    $this->assertSaveLayout();
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('The NEW block body');
+    $assert_session->pageTextNotContains('The DEFAULT block body');
+
+    $revision_url = "node/1/revisions/$original_revision_id";
+
+    // Ensure viewing the previous revision shows the previous block revision.
+    $this->drupalGet("$revision_url/view");
+    $assert_session->pageTextContains('The DEFAULT block body');
+    $assert_session->pageTextNotContains('The NEW block body');
+
+    // Revert to first revision.
+    $revision_url = "$revision_url/revert";
+    $this->drupalGet($revision_url);
+    $page->pressButton('Revert');
+
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('The DEFAULT block body');
+    $assert_session->pageTextNotContains('The NEW block body');
+  }
+
+  /**
+   * Tests that entity blocks deleted correctly.
+   *
+   * @throws \Behat\Mink\Exception\ExpectationException
+   * @throws \Behat\Mink\Exception\ResponseTextException
+   */
+  public function testDeletion() {
+    /** @var \Drupal\Core\Cron $cron */
+    $cron = \Drupal::service('cron');
+    $this->drupalLogin($this->drupalCreateUser([
+      'administer content types',
+      'access contextual links',
+      'configure any layout',
+      'administer node display',
+      'administer node fields',
+      'administer nodes',
+      'bypass node access',
+    ]));
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    // Add a block to default layout.
+    $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
+    $this->drupalGet($field_ui_prefix . '/display/default');
+    $this->clickLink('Manage layout');
+    $assert_session->addressEquals("$field_ui_prefix/display-layout/default");
+    $this->addInlineBlockToLayout('Block title', 'The DEFAULT block body');
+    $this->assertSaveLayout();
+
+    $this->assertCount(1, $this->blockStorage->loadMultiple());
+    $default_block_id = $this->getLatestBlockEntityId();
+
+    // Ensure the block shows up on node pages.
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('The DEFAULT block body');
+    $this->drupalGet('node/2');
+    $assert_session->pageTextContains('The DEFAULT block body');
+
+    // Enable overrides.
+    $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
+
+    // Ensure we have 2 copies of the block in node overrides.
+    $this->drupalGet('node/1/layout');
+    $this->assertSaveLayout();
+    $node_1_block_id = $this->getLatestBlockEntityId();
+
+    $this->drupalGet('node/2/layout');
+    $this->assertSaveLayout();
+    $node_2_block_id = $this->getLatestBlockEntityId();
+    $this->assertCount(3, $this->blockStorage->loadMultiple());
+
+    $this->drupalGet($field_ui_prefix . '/display/default');
+    $this->clickLink('Manage layout');
+    $assert_session->addressEquals("$field_ui_prefix/display-layout/default");
+
+    $this->assertNotEmpty($this->blockStorage->load($default_block_id));
+    // Remove block from default.
+    $this->removeInlineBlockFromLayout();
+    $this->assertSaveLayout();
+    // Ensure the block in the default was deleted.
+    $this->blockStorage->resetCache([$default_block_id]);
+    $this->assertEmpty($this->blockStorage->load($default_block_id));
+    // Ensure other blocks still exist.
+    $this->assertCount(2, $this->blockStorage->loadMultiple());
+
+    $this->drupalGet('node/1/layout');
+    $assert_session->pageTextContains('The DEFAULT block body');
+
+    $this->removeInlineBlockFromLayout();
+    $this->assertSaveLayout();
+    $cron->run();
+    // Ensure entity block is not deleted because it is needed in revision.
+    $this->assertNotEmpty($this->blockStorage->load($node_1_block_id));
+    $this->assertCount(2, $this->blockStorage->loadMultiple());
+
+    // Ensure entity block is deleted when node is deleted.
+    $this->drupalGet('node/1/delete');
+    $page->pressButton('Delete');
+    $this->assertEmpty(Node::load(1));
+    $cron->run();
+    $this->assertEmpty($this->blockStorage->load($node_1_block_id));
+    $this->assertCount(1, $this->blockStorage->loadMultiple());
+
+    // Add another block to the default.
+    $this->drupalGet($field_ui_prefix . '/display/default');
+    $this->clickLink('Manage layout');
+    $assert_session->addressEquals("$field_ui_prefix/display-layout/default");
+    $this->addInlineBlockToLayout('Title 2', 'Body 2');
+    $this->assertSaveLayout();
+    $cron->run();
+    $default_block2_id = $this->getLatestBlockEntityId();
+    $this->assertCount(2, $this->blockStorage->loadMultiple());
+
+    // Delete the other node so bundle can be deleted.
+    $this->drupalGet('node/2/delete');
+    $page->pressButton('Delete');
+    $this->assertEmpty(Node::load(2));
+    $cron->run();
+    // Ensure entity block was deleted.
+    $this->assertEmpty($this->blockStorage->load($node_2_block_id));
+    $this->assertCount(1, $this->blockStorage->loadMultiple());
+
+    // Delete the bundle which has the default layout.
+    $this->drupalGet("$field_ui_prefix/delete");
+    $page->pressButton('Delete');
+    $cron->run();
+
+    // Ensure the entity block in default is deleted when bundle is deleted.
+    $this->assertEmpty($this->blockStorage->load($default_block2_id));
+    $this->assertCount(0, $this->blockStorage->loadMultiple());
+  }
+
+  /**
+   * Gets the latest block entity id.
+   */
+  protected function getLatestBlockEntityId() {
+    $block_ids = \Drupal::entityQuery('block_content')->sort('id', 'DESC')->range(0, 1)->execute();
+    $block_id = array_pop($block_ids);
+    $this->assertNotEmpty($this->blockStorage->load($block_id));
+    return $block_id;
+  }
+
+  /**
+   * Removes an entity block from the layout but does not save the layout.
+   */
+  protected function removeInlineBlockFromLayout() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+    $rendered_block = $page->find('css', static::INLINE_BLOCK_LOCATOR)->getText();
+    $this->assertNotEmpty($rendered_block);
+    $assert_session->pageTextContains($rendered_block);
+    $this->clickContextualLink(static::INLINE_BLOCK_LOCATOR, 'Remove block');
+    $assert_session->assertWaitOnAjaxRequest();
+    $page->find('css', '#drupal-off-canvas')->pressButton('Remove');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->pageTextNotContains($rendered_block);
+  }
+
+  /**
+   * Adds an entity block to the layout.
+   *
+   * @param string $title
+   *   The title field value.
+   * @param string $body
+   *   The body field value.
+   *
+   * @throws \Behat\Mink\Exception\ElementNotFoundException
+   * @throws \Behat\Mink\Exception\ExpectationException
+   */
+  protected function addInlineBlockToLayout($title, $body) {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+    $page->clickLink('Add Block');
+    $assert_session->assertWaitOnAjaxRequest();
+    $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.block-categories details:contains(Create new block)'));
+    $this->clickLink('Basic block');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->fieldValueEquals('Title', '');
+    $page->findField('Title')->setValue($title);
+    $textarea = $assert_session->elementExists('css', '[name="settings[block_form][body][0][value]"]');
+    $textarea->setValue($body);
+    $page->pressButton('Add Block');
+    // @todo Replace with 'assertNoElementAfterWait()' after
+    // https://www.drupal.org/project/drupal/issues/2892440.
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->elementNotExists('css', '#drupal-off-canvas');
+    $found_new_text = FALSE;
+    /** @var \Behat\Mink\Element\NodeElement $element */
+    foreach ($page->findAll('css', static::INLINE_BLOCK_LOCATOR) as $element) {
+      if (stristr($element->getText(), $body)) {
+        $found_new_text = TRUE;
+        break;
+      }
+    }
+    $this->assertNotEmpty($found_new_text, 'Found block text on page.');
+  }
+
+  /**
+   * Configures an inline block in the Layout Builder.
+   *
+   * @param string $old_body
+   *   The old body field value.
+   * @param string $new_body
+   *   The new body field value.
+   * @param string $block_css_locator
+   *   The CSS locator to use to select the contextual link.
+   */
+  protected function configureInlineBlock($old_body, $new_body, $block_css_locator = NULL) {
+    $block_css_locator = $block_css_locator ?: static::INLINE_BLOCK_LOCATOR;
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+    $this->clickContextualLink($block_css_locator, 'Configure');
+    $textarea = $assert_session->waitForElementVisible('css', '[name="settings[block_form][body][0][value]"]');
+    $this->assertNotEmpty($textarea);
+    $this->assertSame($old_body, $textarea->getValue());
+    $textarea->setValue($new_body);
+    $page->pressButton('Update');
+    $assert_session->assertWaitOnAjaxRequest();
+  }
+
+}
