From c6cdc4392324b4ee18f0492bbc6ea53c2f918250 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Tue, 24 Jul 2018 20:51:45 +0100
Subject: [PATCH] Issue #2936358 by tim.plunkett, johndevman, tedbow, eiriksm,
 AaronMcHale, phenaproxima, pookmish, alexpott: Layout Builder should be
 opt-in per display (entity type/bundle/view mode)

---
 .../config/schema/layout_builder.schema.yml   |   3 +
 .../layout_builder/layout_builder.install     |  44 ++++-
 .../layout_builder.post_update.php            |   8 +-
 .../layout_builder/layout_builder.routing.yml |   9 +
 .../layout_builder.services.yml               |   4 +
 .../src/Access/LayoutBuilderAccessCheck.php   |  40 ++++
 .../src/DefaultsSectionStorageInterface.php   |   4 +-
 .../Entity/LayoutBuilderEntityViewDisplay.php |  53 +++++
 .../Entity/LayoutEntityDisplayInterface.php   |   5 +-
 .../src/Form/LayoutBuilderDisableForm.php     | 106 ++++++++++
 .../LayoutBuilderEntityViewDisplayForm.php    | 102 ++++++++--
 .../src/LayoutBuilderEnabledInterface.php     |  32 +++
 .../SectionStorage/DefaultsSectionStorage.php |  37 +++-
 .../OverridesSectionStorage.php               |  15 +-
 .../src/Routing/LayoutBuilderRoutesTrait.php  |  13 ++
 .../src/SectionStorageInterface.php           |   9 +-
 .../fixtures/update/layout-builder-enable.php |  44 +++++
 .../fixtures/update/layout-builder-extra.php  |  29 +++
 .../tests/fixtures/update/layout-builder.php  |  11 ++
 .../src/Functional/LayoutBuilderTest.php      |  14 +-
 .../src/Functional/LayoutSectionTest.php      |   1 +
 .../Update/ExtraFieldUpdatePathTest.php       |  14 +-
 .../LayoutBuilderEnableUpdatePathTest.php     |  86 ++++++++
 .../FunctionalJavascript/AjaxBlockTest.php    |   2 +-
 .../ItemLayoutFieldBlockTest.php              |   5 +-
 .../LayoutBuilderOptInTest.php                | 184 ++++++++++++++++++
 .../LayoutBuilderCompatibilityTestBase.php    |  12 +-
 ...outBuilderFieldLayoutCompatibilityTest.php |   1 +
 .../src/Kernel/LayoutBuilderInstallTest.php   |   2 +
 .../src/Kernel/LayoutSectionItemListTest.php  |   5 +-
 .../src/Unit/DefaultsSectionStorageTest.php   |  45 +++++
 .../src/Unit/OverridesSectionStorageTest.php  |   8 +
 32 files changed, 900 insertions(+), 47 deletions(-)
 create mode 100644 core/modules/layout_builder/src/Access/LayoutBuilderAccessCheck.php
 create mode 100644 core/modules/layout_builder/src/Form/LayoutBuilderDisableForm.php
 create mode 100644 core/modules/layout_builder/src/LayoutBuilderEnabledInterface.php
 create mode 100644 core/modules/layout_builder/tests/fixtures/update/layout-builder-enable.php
 create mode 100644 core/modules/layout_builder/tests/fixtures/update/layout-builder-extra.php
 create mode 100644 core/modules/layout_builder/tests/src/Functional/Update/LayoutBuilderEnableUpdatePathTest.php
 create mode 100644 core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderOptInTest.php

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 682caa78c45e..b6b5fa634214 100644
--- a/core/modules/layout_builder/config/schema/layout_builder.schema.yml
+++ b/core/modules/layout_builder/config/schema/layout_builder.schema.yml
@@ -2,6 +2,9 @@ core.entity_view_display.*.*.*.third_party.layout_builder:
   type: mapping
   label: 'Per-view-mode Layout Builder settings'
   mapping:
+    enabled:
+      type: boolean
+      label: 'Whether the Layout Builder is enabled for this display'
     allow_custom:
       type: boolean
       label: 'Allow a customized layout'
diff --git a/core/modules/layout_builder/layout_builder.install b/core/modules/layout_builder/layout_builder.install
index acb1e4fdf3d9..1bb2a367adf5 100644
--- a/core/modules/layout_builder/layout_builder.install
+++ b/core/modules/layout_builder/layout_builder.install
@@ -13,6 +13,8 @@
  * Implements hook_install().
  */
 function layout_builder_install() {
+  $display_changed = FALSE;
+
   $displays = LayoutBuilderEntityViewDisplay::loadMultiple();
   /** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface[] $displays */
   foreach ($displays as $display) {
@@ -20,21 +22,43 @@ function layout_builder_install() {
     $field_layout = $display->getThirdPartySettings('field_layout');
     if (isset($field_layout['id'])) {
       $field_layout += ['settings' => []];
-      $display->appendSection(new Section($field_layout['id'], $field_layout['settings']));
-    }
-
-    // Sort the components by weight.
-    $components = $display->get('content');
-    uasort($components, 'Drupal\Component\Utility\SortArray::sortByWeightElement');
-    foreach ($components as $name => $component) {
-      $display->setComponent($name, $component);
+      $display
+        ->enableLayoutBuilder()
+        ->appendSection(new Section($field_layout['id'], $field_layout['settings']))
+        ->save();
+      $display_changed = TRUE;
     }
-    $display->save();
   }
 
   // Clear the rendered cache to ensure the new layout builder flow is used.
   // While in many cases the above change will not affect the rendered output,
   // the cacheability metadata will have changed and should be processed to
   // prepare for future changes.
-  Cache::invalidateTags(['rendered']);
+  if ($display_changed) {
+    Cache::invalidateTags(['rendered']);
+  }
+}
+
+/**
+ * Enable Layout Builder for existing entity displays.
+ */
+function layout_builder_update_8601(&$sandbox) {
+  $config_factory = \Drupal::configFactory();
+
+  if (!isset($sandbox['count'])) {
+    $sandbox['ids'] = $config_factory->listAll('core.entity_view_display.');
+    $sandbox['count'] = count($sandbox['ids']);
+  }
+
+  $ids = array_splice($sandbox['ids'], 0, 50);
+  foreach ($ids as $id) {
+    $display = $config_factory->getEditable($id);
+    if ($display->get('third_party_settings.layout_builder')) {
+      $display
+        ->set('third_party_settings.layout_builder.enabled', TRUE)
+        ->save();
+    }
+  }
+
+  $sandbox['#finished'] = empty($sandbox['ids']) ? 1 : ($sandbox['count'] - count($sandbox['ids'])) / $sandbox['count'];
 }
diff --git a/core/modules/layout_builder/layout_builder.post_update.php b/core/modules/layout_builder/layout_builder.post_update.php
index 8ddd3fba986f..1fcd74d43734 100644
--- a/core/modules/layout_builder/layout_builder.post_update.php
+++ b/core/modules/layout_builder/layout_builder.post_update.php
@@ -6,7 +6,7 @@
  */
 
 use Drupal\Core\Config\Entity\ConfigEntityUpdater;
-use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
 
 /**
  * Rebuild plugin dependencies for all entity view displays.
@@ -39,7 +39,11 @@ function layout_builder_post_update_rebuild_plugin_dependencies(&$sandbox = NULL
  */
 function layout_builder_post_update_add_extra_fields(&$sandbox = NULL) {
   $entity_field_manager = \Drupal::service('entity_field.manager');
-  \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'entity_view_display', function (EntityViewDisplayInterface $display) use ($entity_field_manager) {
+  \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'entity_view_display', function (LayoutEntityDisplayInterface $display) use ($entity_field_manager) {
+    if (!$display->isLayoutBuilderEnabled()) {
+      return FALSE;
+    }
+
     $extra_fields = $entity_field_manager->getExtraFields($display->getTargetEntityTypeId(), $display->getTargetBundle());
     $components = $display->getComponents();
     // Sort the components to avoid them being reordered by setComponent().
diff --git a/core/modules/layout_builder/layout_builder.routing.yml b/core/modules/layout_builder/layout_builder.routing.yml
index 322e2301c11b..54c9cf25b23a 100644
--- a/core/modules/layout_builder/layout_builder.routing.yml
+++ b/core/modules/layout_builder/layout_builder.routing.yml
@@ -4,6 +4,7 @@ layout_builder.choose_section:
    _controller: '\Drupal\layout_builder\Controller\ChooseSectionController::build'
   requirements:
     _permission: 'configure any layout'
+    _layout_builder_access: 'view'
   options:
     _admin_route: TRUE
     parameters:
@@ -16,6 +17,7 @@ layout_builder.add_section:
     _controller: '\Drupal\layout_builder\Controller\AddSectionController::build'
   requirements:
     _permission: 'configure any layout'
+    _layout_builder_access: 'view'
   options:
     _admin_route: TRUE
     parameters:
@@ -32,6 +34,7 @@ layout_builder.configure_section:
     plugin_id: null
   requirements:
     _permission: 'configure any layout'
+    _layout_builder_access: 'view'
   options:
     _admin_route: TRUE
     parameters:
@@ -44,6 +47,7 @@ layout_builder.remove_section:
     _form: '\Drupal\layout_builder\Form\RemoveSectionForm'
   requirements:
     _permission: 'configure any layout'
+    _layout_builder_access: 'view'
   options:
     _admin_route: TRUE
     parameters:
@@ -56,6 +60,7 @@ layout_builder.choose_block:
     _controller: '\Drupal\layout_builder\Controller\ChooseBlockController::build'
   requirements:
     _permission: 'configure any layout'
+    _layout_builder_access: 'view'
   options:
     _admin_route: TRUE
     parameters:
@@ -68,6 +73,7 @@ layout_builder.add_block:
     _form: '\Drupal\layout_builder\Form\AddBlockForm'
   requirements:
     _permission: 'configure any layout'
+    _layout_builder_access: 'view'
   options:
     _admin_route: TRUE
     parameters:
@@ -80,6 +86,7 @@ layout_builder.update_block:
     _form: '\Drupal\layout_builder\Form\UpdateBlockForm'
   requirements:
     _permission: 'configure any layout'
+    _layout_builder_access: 'view'
   options:
     _admin_route: TRUE
     parameters:
@@ -92,6 +99,7 @@ layout_builder.remove_block:
     _form: '\Drupal\layout_builder\Form\RemoveBlockForm'
   requirements:
     _permission: 'configure any layout'
+    _layout_builder_access: 'view'
   options:
     _admin_route: TRUE
     parameters:
@@ -110,6 +118,7 @@ layout_builder.move_block:
     preceding_block_uuid: null
   requirements:
     _permission: 'configure any layout'
+    _layout_builder_access: 'view'
   options:
     _admin_route: TRUE
     parameters:
diff --git a/core/modules/layout_builder/layout_builder.services.yml b/core/modules/layout_builder/layout_builder.services.yml
index 4fe50929bd48..f2360a50ff40 100644
--- a/core/modules/layout_builder/layout_builder.services.yml
+++ b/core/modules/layout_builder/layout_builder.services.yml
@@ -2,6 +2,10 @@ services:
   layout_builder.tempstore_repository:
     class: Drupal\layout_builder\LayoutTempstoreRepository
     arguments: ['@tempstore.shared']
+  access_check.entity.layout_builder_access:
+    class: Drupal\layout_builder\Access\LayoutBuilderAccessCheck
+    tags:
+      - { name: access_check, applies_to: _layout_builder_access }
   access_check.entity.layout:
     class: Drupal\layout_builder\Access\LayoutSectionAccessCheck
     tags:
diff --git a/core/modules/layout_builder/src/Access/LayoutBuilderAccessCheck.php b/core/modules/layout_builder/src/Access/LayoutBuilderAccessCheck.php
new file mode 100644
index 000000000000..0a70db063301
--- /dev/null
+++ b/core/modules/layout_builder/src/Access/LayoutBuilderAccessCheck.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\layout_builder\Access;
+
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\layout_builder\SectionStorageInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Provides an access check for the Layout Builder defaults.
+ *
+ * @internal
+ */
+class LayoutBuilderAccessCheck implements AccessInterface {
+
+  /**
+   * Checks routing access to the layout.
+   *
+   * @param \Drupal\layout_builder\SectionStorageInterface $section_storage
+   *   The section storage.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The current user.
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route to check against.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function access(SectionStorageInterface $section_storage, AccountInterface $account, Route $route) {
+    $operation = $route->getRequirement('_layout_builder_access');
+    $access = $section_storage->access($operation, $account, TRUE);
+    if ($access instanceof RefinableCacheableDependencyInterface) {
+      $access->addCacheableDependency($section_storage);
+    }
+    return $access;
+  }
+
+}
diff --git a/core/modules/layout_builder/src/DefaultsSectionStorageInterface.php b/core/modules/layout_builder/src/DefaultsSectionStorageInterface.php
index 121d9c84eb63..0bcaa387e026 100644
--- a/core/modules/layout_builder/src/DefaultsSectionStorageInterface.php
+++ b/core/modules/layout_builder/src/DefaultsSectionStorageInterface.php
@@ -11,8 +11,10 @@
  *   Layout Builder is currently experimental and should only be leveraged by
  *   experimental modules and development releases of contributed modules.
  *   See https://www.drupal.org/core/experimental for more information.
+ *
+ * @todo Refactor this interface in https://www.drupal.org/node/2985362.
  */
-interface DefaultsSectionStorageInterface extends SectionStorageInterface, ThirdPartySettingsInterface {
+interface DefaultsSectionStorageInterface extends SectionStorageInterface, ThirdPartySettingsInterface, LayoutBuilderEnabledInterface {
 
   /**
    * Determines if the defaults allow custom overrides.
diff --git a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php
index ea0cd12ff0ab..5ab2d7264c74 100644
--- a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php
+++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php
@@ -58,6 +58,30 @@ public function setOverridable($overridable = TRUE) {
     return $this;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isLayoutBuilderEnabled() {
+    return (bool) $this->getThirdPartySetting('layout_builder', 'enabled');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function enableLayoutBuilder() {
+    $this->setThirdPartySetting('layout_builder', 'enabled', TRUE);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function disableLayoutBuilder() {
+    $this->setOverridable(FALSE);
+    $this->setThirdPartySetting('layout_builder', 'enabled', FALSE);
+    return $this;
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -92,6 +116,27 @@ public function preSave(EntityStorageInterface $storage) {
         $field->delete();
       }
     }
+
+    $already_enabled = isset($this->original) ? $this->original->isLayoutBuilderEnabled() : FALSE;
+    $set_enabled = $this->isLayoutBuilderEnabled();
+    if ($already_enabled !== $set_enabled) {
+      if ($set_enabled) {
+        // Loop through all existing field-based components and add them as
+        // section-based components.
+        $components = $this->getComponents();
+        // Sort the components by weight.
+        uasort($components, 'Drupal\Component\Utility\SortArray::sortByWeightElement');
+        foreach ($components as $name => $component) {
+          $this->setComponent($name, $component);
+        }
+      }
+      else {
+        // When being disabled, remove all existing section data.
+        while (count($this) > 0) {
+          $this->removeSection(0);
+        }
+      }
+    }
   }
 
   /**
@@ -153,6 +198,9 @@ protected function contextRepository() {
    */
   public function buildMultiple(array $entities) {
     $build_list = parent::buildMultiple($entities);
+    if (!$this->isLayoutBuilderEnabled()) {
+      return $build_list;
+    }
 
     /** @var \Drupal\Core\Entity\EntityInterface $entity */
     foreach ($entities as $id => $entity) {
@@ -272,6 +320,11 @@ public function setComponent($name, array $options = []) {
       return $this;
     }
 
+    // Only continue if Layout Builder is enabled.
+    if (!$this->isLayoutBuilderEnabled()) {
+      return $this;
+    }
+
     // Retrieve the updated options after the parent:: call.
     $options = $this->content[$name];
     // Provide backwards compatibility by converting to a section component.
diff --git a/core/modules/layout_builder/src/Entity/LayoutEntityDisplayInterface.php b/core/modules/layout_builder/src/Entity/LayoutEntityDisplayInterface.php
index b8fd21d822b2..51151bd81c10 100644
--- a/core/modules/layout_builder/src/Entity/LayoutEntityDisplayInterface.php
+++ b/core/modules/layout_builder/src/Entity/LayoutEntityDisplayInterface.php
@@ -3,6 +3,7 @@
 namespace Drupal\layout_builder\Entity;
 
 use Drupal\Core\Entity\Display\EntityDisplayInterface;
+use Drupal\layout_builder\LayoutBuilderEnabledInterface;
 use Drupal\layout_builder\SectionListInterface;
 
 /**
@@ -12,8 +13,10 @@
  *   Layout Builder is currently experimental and should only be leveraged by
  *   experimental modules and development releases of contributed modules.
  *   See https://www.drupal.org/core/experimental for more information.
+ *
+ * @todo Refactor this interface in https://www.drupal.org/node/2985362.
  */
-interface LayoutEntityDisplayInterface extends EntityDisplayInterface, SectionListInterface {
+interface LayoutEntityDisplayInterface extends EntityDisplayInterface, SectionListInterface, LayoutBuilderEnabledInterface {
 
   /**
    * Determines if the display allows custom overrides.
diff --git a/core/modules/layout_builder/src/Form/LayoutBuilderDisableForm.php b/core/modules/layout_builder/src/Form/LayoutBuilderDisableForm.php
new file mode 100644
index 000000000000..48b42b740daa
--- /dev/null
+++ b/core/modules/layout_builder/src/Form/LayoutBuilderDisableForm.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Drupal\layout_builder\Form;
+
+use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\layout_builder\DefaultsSectionStorageInterface;
+use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
+use Drupal\layout_builder\SectionStorageInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Disables Layout Builder for a given default.
+ */
+class LayoutBuilderDisableForm extends ConfirmFormBase {
+
+  /**
+   * The layout tempstore repository.
+   *
+   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
+   */
+  protected $layoutTempstoreRepository;
+
+  /**
+   * The section storage.
+   *
+   * @var \Drupal\layout_builder\DefaultsSectionStorageInterface
+   */
+  protected $sectionStorage;
+
+  /**
+   * Constructs a new RevertOverridesForm.
+   *
+   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
+   *   The layout tempstore repository.
+   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
+   *   The messenger service.
+   */
+  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, MessengerInterface $messenger) {
+    $this->layoutTempstoreRepository = $layout_tempstore_repository;
+    $this->setMessenger($messenger);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('layout_builder.tempstore_repository'),
+      $container->get('messenger')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'layout_builder_disable_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    return $this->t('Are you sure you want to disable Layout Builder?');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return $this->t('All customizations will be removed. This action cannot be undone.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCancelUrl() {
+    return $this->sectionStorage->getRedirectUrl();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL) {
+    if (!$section_storage instanceof DefaultsSectionStorageInterface) {
+      throw new \InvalidArgumentException(sprintf('The section storage with type "%s" and ID "%s" does not provide defaults', $section_storage->getStorageType(), $section_storage->getStorageId()));
+    }
+
+    $this->sectionStorage = $section_storage;
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $this->sectionStorage->disableLayoutBuilder()->save();
+    $this->layoutTempstoreRepository->delete($this->sectionStorage);
+
+    $this->messenger()->addMessage($this->t('Layout Builder has been disabled.'));
+    $form_state->setRedirectUrl($this->getCancelUrl());
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Form/LayoutBuilderEntityViewDisplayForm.php b/core/modules/layout_builder/src/Form/LayoutBuilderEntityViewDisplayForm.php
index 63ec398eec25..b24549d9efa8 100644
--- a/core/modules/layout_builder/src/Form/LayoutBuilderEntityViewDisplayForm.php
+++ b/core/modules/layout_builder/src/Form/LayoutBuilderEntityViewDisplayForm.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\layout_builder\Form;
 
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\field_ui\Form\EntityViewDisplayEditForm;
@@ -28,7 +29,7 @@ class LayoutBuilderEntityViewDisplayForm extends EntityViewDisplayEditForm {
   /**
    * The storage section.
    *
-   * @var \Drupal\layout_builder\SectionStorageInterface
+   * @var \Drupal\layout_builder\DefaultsSectionStorageInterface
    */
   protected $sectionStorage;
 
@@ -46,10 +47,17 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt
   public function form(array $form, FormStateInterface $form_state) {
     $form = parent::form($form, $form_state);
 
-    // Hide the table of fields.
-    $form['fields']['#access'] = FALSE;
-    $form['#fields'] = [];
-    $form['#extra'] = [];
+    $is_enabled = $this->entity->isLayoutBuilderEnabled();
+    if ($is_enabled) {
+      // Hide the table of fields.
+      $form['fields']['#access'] = FALSE;
+      $form['#fields'] = [];
+      $form['#extra'] = [];
+    }
+    else {
+      // Remove the Layout Builder field from the list.
+      $form['#fields'] = array_diff($form['#fields'], ['layout_builder__layout']);
+    }
 
     $form['manage_layout'] = [
       '#type' => 'link',
@@ -57,18 +65,26 @@ public function form(array $form, FormStateInterface $form_state) {
       '#weight' => -10,
       '#attributes' => ['class' => ['button']],
       '#url' => $this->sectionStorage->getLayoutBuilderUrl(),
+      '#access' => $is_enabled,
+    ];
+
+    $form['layout'] = [
+      '#type' => 'details',
+      '#open' => TRUE,
+      '#title' => $this->t('Layout options'),
+      '#tree' => TRUE,
     ];
 
+    $form['layout']['enabled'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Use Layout Builder'),
+      '#default_value' => $is_enabled,
+    ];
+    $form['#entity_builders']['layout_builder'] = '::entityFormEntityBuild';
+
     // @todo Expand to work for all view modes in
     //   https://www.drupal.org/node/2907413.
     if ($this->entity->getMode() === 'default') {
-      $form['layout'] = [
-        '#type' => 'details',
-        '#open' => TRUE,
-        '#title' => $this->t('Layout options'),
-        '#tree' => TRUE,
-      ];
-
       $entity_type = $this->entityTypeManager->getDefinition($this->entity->getTargetEntityTypeId());
       $form['layout']['allow_custom'] = [
         '#type' => 'checkbox',
@@ -76,14 +92,26 @@ public function form(array $form, FormStateInterface $form_state) {
           '@entity' => $entity_type->getSingularLabel(),
         ]),
         '#default_value' => $this->entity->isOverridable(),
+        '#states' => [
+          'disabled' => [
+            ':input[name="layout[enabled]"]' => ['checked' => FALSE],
+          ],
+          'invisible' => [
+            ':input[name="layout[enabled]"]' => ['checked' => FALSE],
+          ],
+        ],
       ];
+      if (!$is_enabled) {
+        $form['layout']['allow_custom']['#attributes']['disabled'] = 'disabled';
+      }
       // Prevent turning off overrides while any exist.
       if ($this->hasOverrides($this->entity)) {
+        $form['layout']['enabled']['#disabled'] = TRUE;
+        $form['layout']['enabled']['#description'] = $this->t('You must revert all customized layouts of this display before you can disable this option.');
         $form['layout']['allow_custom']['#disabled'] = TRUE;
         $form['layout']['allow_custom']['#description'] = $this->t('You must revert all customized layouts of this display before you can disable this option.');
-      }
-      else {
-        $form['#entity_builders'][] = '::entityFormEntityBuild';
+        unset($form['layout']['allow_custom']['#states']);
+        unset($form['#entity_builders']['layout_builder']);
       }
     }
     return $form;
@@ -112,26 +140,62 @@ protected function hasOverrides(LayoutEntityDisplayInterface $display) {
     return (bool) $query->count()->execute();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
+    // Do not process field values if Layout Builder is or will be enabled.
+    $set_enabled = (bool) $form_state->getValue(['layout', 'enabled'], FALSE);
+    /** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface $entity */
+    $already_enabled = $entity->isLayoutBuilderEnabled();
+    if ($already_enabled || $set_enabled) {
+      $form['#fields'] = [];
+      $form['#extra'] = [];
+    }
+
+    parent::copyFormValuesToEntity($entity, $form, $form_state);
+  }
+
   /**
    * Entity builder for layout options on the entity view display form.
    */
   public function entityFormEntityBuild($entity_type_id, LayoutEntityDisplayInterface $display, &$form, FormStateInterface &$form_state) {
-    $new_value = (bool) $form_state->getValue(['layout', 'allow_custom'], FALSE);
-    $display->setOverridable($new_value);
+    $set_enabled = (bool) $form_state->getValue(['layout', 'enabled'], FALSE);
+    $already_enabled = $display->isLayoutBuilderEnabled();
+
+    if ($set_enabled) {
+      $overridable = (bool) $form_state->getValue(['layout', 'allow_custom'], FALSE);
+      $display->setOverridable($overridable);
+
+      if (!$already_enabled) {
+        $display->enableLayoutBuilder();
+      }
+    }
+    elseif ($already_enabled) {
+      $form_state->setRedirectUrl($this->sectionStorage->getLayoutBuilderUrl('disable'));
+    }
   }
 
   /**
    * {@inheritdoc}
    */
   protected function buildFieldRow(FieldDefinitionInterface $field_definition, array $form, FormStateInterface $form_state) {
-    // Intentionally empty.
+    if ($this->entity->isLayoutBuilderEnabled() || $field_definition->getType() === 'layout_section') {
+      return [];
+    }
+
+    return parent::buildFieldRow($field_definition, $form, $form_state);
   }
 
   /**
    * {@inheritdoc}
    */
   protected function buildExtraFieldRow($field_id, $extra_field) {
-    // Intentionally empty.
+    if ($this->entity->isLayoutBuilderEnabled()) {
+      return [];
+    }
+
+    return parent::buildExtraFieldRow($field_id, $extra_field);
   }
 
 }
diff --git a/core/modules/layout_builder/src/LayoutBuilderEnabledInterface.php b/core/modules/layout_builder/src/LayoutBuilderEnabledInterface.php
new file mode 100644
index 000000000000..dab8fc4bef06
--- /dev/null
+++ b/core/modules/layout_builder/src/LayoutBuilderEnabledInterface.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\layout_builder;
+
+/**
+ * Provides methods for enabling and disabling Layout Builder.
+ */
+interface LayoutBuilderEnabledInterface {
+
+  /**
+   * Determines if Layout Builder is enabled.
+   *
+   * @return bool
+   *   TRUE if Layout Builder is enabled, FALSE otherwise.
+   */
+  public function isLayoutBuilderEnabled();
+
+  /**
+   * Enables the Layout Builder.
+   *
+   * @return $this
+   */
+  public function enableLayoutBuilder();
+
+  /**
+   * Disables the Layout Builder.
+   *
+   * @return $this
+   */
+  public function disableLayoutBuilder();
+
+}
diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php
index a795f18d39bf..6fab11996b02 100644
--- a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php
+++ b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php
@@ -3,12 +3,14 @@
 namespace Drupal\layout_builder\Plugin\SectionStorage;
 
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Plugin\Context\EntityContext;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Url;
 use Drupal\field_ui\FieldUI;
 use Drupal\layout_builder\DefaultsSectionStorageInterface;
@@ -123,8 +125,8 @@ public function getRedirectUrl() {
   /**
    * {@inheritdoc}
    */
-  public function getLayoutBuilderUrl() {
-    return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getDisplay()->getTargetEntityTypeId()}.view", $this->getRouteParameters());
+  public function getLayoutBuilderUrl($rel = 'view') {
+    return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getDisplay()->getTargetEntityTypeId()}.$rel", $this->getRouteParameters());
   }
 
   /**
@@ -296,6 +298,29 @@ public function setThirdPartySetting($module, $key, $value) {
     return $this;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isLayoutBuilderEnabled() {
+    return $this->getDisplay()->isLayoutBuilderEnabled();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function enableLayoutBuilder() {
+    $this->getDisplay()->enableLayoutBuilder();
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function disableLayoutBuilder() {
+    $this->getDisplay()->disableLayoutBuilder();
+    return $this;
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -325,4 +350,12 @@ public function getThirdPartyProviders() {
     return $this->getDisplay()->getThirdPartyProviders();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
+    $result = AccessResult::allowedIf($this->isLayoutBuilderEnabled());
+    return $return_as_object ? $result : $result->isAllowed();
+  }
+
 }
diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
index 0da2947dd367..3a92ee63fab7 100644
--- a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
+++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\layout_builder\Plugin\SectionStorage;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -9,6 +10,7 @@
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Plugin\Context\EntityContext;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Url;
 use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
 use Drupal\layout_builder\OverridesSectionStorageInterface;
@@ -200,10 +202,10 @@ public function getRedirectUrl() {
   /**
    * {@inheritdoc}
    */
-  public function getLayoutBuilderUrl() {
+  public function getLayoutBuilderUrl($rel = 'view') {
     $entity = $this->getEntity();
     $route_parameters[$entity->getEntityTypeId()] = $entity->id();
-    return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getEntity()->getEntityTypeId()}.view", $route_parameters);
+    return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getEntity()->getEntityTypeId()}.$rel", $route_parameters);
   }
 
   /**
@@ -229,4 +231,13 @@ public function save() {
     return $this->getEntity()->save();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
+    $default_section_storage = $this->getDefaultSectionStorage();
+    $result = AccessResult::allowedIf($default_section_storage->isLayoutBuilderEnabled())->addCacheableDependency($default_section_storage);
+    return $return_as_object ? $result : $result->isAllowed();
+  }
+
 }
diff --git a/core/modules/layout_builder/src/Routing/LayoutBuilderRoutesTrait.php b/core/modules/layout_builder/src/Routing/LayoutBuilderRoutesTrait.php
index febedbe31efd..d06b95324b98 100644
--- a/core/modules/layout_builder/src/Routing/LayoutBuilderRoutesTrait.php
+++ b/core/modules/layout_builder/src/Routing/LayoutBuilderRoutesTrait.php
@@ -3,6 +3,7 @@
 namespace Drupal\layout_builder\Routing;
 
 use Drupal\Component\Utility\NestedArray;
+use Drupal\layout_builder\DefaultsSectionStorageInterface;
 use Drupal\layout_builder\OverridesSectionStorageInterface;
 use Drupal\layout_builder\SectionStorage\SectionStorageDefinition;
 use Symfony\Component\Routing\Route;
@@ -43,6 +44,7 @@ protected function buildLayoutRoutes(RouteCollection $collection, SectionStorage
     $defaults['section_storage'] = '';
     // Trigger the layout builder access check.
     $requirements['_has_layout_section'] = 'true';
+    $requirements['_layout_builder_access'] = 'view';
     // Trigger the layout builder RouteEnhancer.
     $options['_layout_builder'] = TRUE;
     // Trigger the layout builder param converter.
@@ -92,6 +94,17 @@ protected function buildLayoutRoutes(RouteCollection $collection, SectionStorage
         ->setOptions($options);
       $collection->add("$route_name_prefix.revert", $route);
     }
+    elseif (is_subclass_of($definition->getClass(), DefaultsSectionStorageInterface::class)) {
+      $disable_defaults = $defaults;
+      $disable_defaults['_form'] = '\Drupal\layout_builder\Form\LayoutBuilderDisableForm';
+      $disable_options = $options;
+      unset($disable_options['_admin_route'], $disable_options['_layout_builder']);
+      $route = (new Route("$path/disable"))
+        ->setDefaults($disable_defaults)
+        ->setRequirements($requirements)
+        ->setOptions($disable_options);
+      $collection->add("$route_name_prefix.disable", $route);
+    }
   }
 
 }
diff --git a/core/modules/layout_builder/src/SectionStorageInterface.php b/core/modules/layout_builder/src/SectionStorageInterface.php
index e7fbb086b89d..90ce9072fddd 100644
--- a/core/modules/layout_builder/src/SectionStorageInterface.php
+++ b/core/modules/layout_builder/src/SectionStorageInterface.php
@@ -3,6 +3,7 @@
 namespace Drupal\layout_builder;
 
 use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\Core\Access\AccessibleInterface;
 use Symfony\Component\Routing\RouteCollection;
 
 /**
@@ -13,7 +14,7 @@
  *   experimental modules and development releases of contributed modules.
  *   See https://www.drupal.org/core/experimental for more information.
  */
-interface SectionStorageInterface extends SectionListInterface, PluginInspectionInterface {
+interface SectionStorageInterface extends SectionListInterface, PluginInspectionInterface, AccessibleInterface {
 
   /**
    * Returns an identifier for this storage.
@@ -88,10 +89,14 @@ public function getRedirectUrl();
   /**
    * Gets the URL used to display the Layout Builder UI.
    *
+   * @param string $rel
+   *   (optional) The link relationship type, for example: 'view' or 'disable'.
+   *   Defaults to 'view'.
+   *
    * @return \Drupal\Core\Url
    *   The URL object.
    */
-  public function getLayoutBuilderUrl();
+  public function getLayoutBuilderUrl($rel = 'view');
 
   /**
    * Configures the plugin based on route values.
diff --git a/core/modules/layout_builder/tests/fixtures/update/layout-builder-enable.php b/core/modules/layout_builder/tests/fixtures/update/layout-builder-enable.php
new file mode 100644
index 000000000000..fc69baae3375
--- /dev/null
+++ b/core/modules/layout_builder/tests/fixtures/update/layout-builder-enable.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Test fixture.
+ */
+
+use Drupal\Core\Database\Database;
+
+$connection = Database::getConnection();
+
+// Add a layout plugin to an existing entity view display without explicitly
+// enabling Layout Builder for this display.
+$display = $connection->select('config')
+  ->fields('config', ['data'])
+  ->condition('collection', '')
+  ->condition('name', 'core.entity_view_display.block_content.basic.default')
+  ->execute()
+  ->fetchField();
+$display = unserialize($display);
+$display['third_party_settings']['layout_builder']['sections'][] = [
+  'layout_id' => 'layout_onecol',
+  'layout_settings' => [],
+  'components' => [
+    'some-uuid' => [
+      'uuid' => 'some-uuid',
+      'region' => 'content',
+      'configuration' => [
+        'id' => 'system_powered_by_block',
+      ],
+      'additional' => [],
+      'weight' => 0,
+    ],
+  ],
+];
+$connection->update('config')
+  ->fields([
+    'data' => serialize($display),
+    'collection' => '',
+    'name' => 'core.entity_view_display.block_content.basic.default',
+  ])
+  ->condition('collection', '')
+  ->condition('name', 'core.entity_view_display.block_content.basic.default')
+  ->execute();
diff --git a/core/modules/layout_builder/tests/fixtures/update/layout-builder-extra.php b/core/modules/layout_builder/tests/fixtures/update/layout-builder-extra.php
new file mode 100644
index 000000000000..7ec1b6954a02
--- /dev/null
+++ b/core/modules/layout_builder/tests/fixtures/update/layout-builder-extra.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Test fixture.
+ */
+
+use Drupal\Core\Database\Database;
+
+$connection = Database::getConnection();
+
+// Enable Layout Builder on an existing entity view display.
+$display = $connection->select('config')
+  ->fields('config', ['data'])
+  ->condition('collection', '')
+  ->condition('name', 'core.entity_view_display.node.article.default')
+  ->execute()
+  ->fetchField();
+$display = unserialize($display);
+$display['third_party_settings']['layout_builder']['enabled'] = TRUE;
+$connection->update('config')
+  ->fields([
+    'data' => serialize($display),
+    'collection' => '',
+    'name' => 'core.entity_view_display.node.article.default',
+  ])
+  ->condition('collection', '')
+  ->condition('name', 'core.entity_view_display.node.article.default')
+  ->execute();
diff --git a/core/modules/layout_builder/tests/fixtures/update/layout-builder.php b/core/modules/layout_builder/tests/fixtures/update/layout-builder.php
index f2e1509934bf..3f5adb81113c 100644
--- a/core/modules/layout_builder/tests/fixtures/update/layout-builder.php
+++ b/core/modules/layout_builder/tests/fixtures/update/layout-builder.php
@@ -9,6 +9,17 @@
 
 $connection = Database::getConnection();
 
+// Set the schema version.
+$connection->merge('key_value')
+  ->fields([
+    'value' => 'i:8000;',
+    'name' => 'layout_builder',
+    'collection' => 'system.schema',
+  ])
+  ->condition('collection', 'system.schema')
+  ->condition('name', 'layout_builder')
+  ->execute();
+
 // Update core.extension.
 $extensions = $connection->select('config')
   ->fields('config', ['data'])
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
index afc0801c1a76..d39c7e673f0a 100644
--- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
@@ -80,6 +80,10 @@ public function testLayoutBuilderUi() {
 
     // From the manage display page, go to manage the layout.
     $this->drupalGet("$field_ui_prefix/display/default");
+    $assert_session->linkNotExists('Manage layout');
+    $assert_session->fieldDisabled('layout[allow_custom]');
+
+    $this->drupalPostForm(NULL, ['layout[enabled]' => TRUE], 'Save');
     $assert_session->linkExists('Manage layout');
     $this->clickLink('Manage layout');
     $assert_session->addressEquals("$field_ui_prefix/display-layout/default");
@@ -153,6 +157,7 @@ public function testLayoutBuilderUi() {
 
     // Assert that overrides cannot be turned off while overrides exist.
     $this->drupalGet("$field_ui_prefix/display/default");
+    $assert_session->checkboxChecked('layout[allow_custom]');
     $assert_session->fieldDisabled('layout[allow_custom]');
 
     // Alter the defaults.
@@ -243,7 +248,9 @@ public function testPluginDependencies() {
     $page->fillField('id', 'myothermenu');
     $page->pressButton('Save');
 
-    $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display-layout/default');
+    $this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display', ['layout[enabled]' => TRUE], 'Save');
+    $assert_session->linkExists('Manage layout');
+    $this->clickLink('Manage layout');
     $assert_session->linkExists('Add Section');
     $this->clickLink('Add Section');
     $assert_session->linkExists('Layout plugin (with dependencies)');
@@ -305,6 +312,7 @@ public function testLayoutBuilderUiFullViewMode() {
 
     $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
     // Allow overrides for the layout.
+    $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save');
     $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
 
     // Customize the default view mode.
@@ -365,7 +373,8 @@ public function testLayoutBuilderChooseBlocksAlter() {
     ]));
 
     // From the manage display page, go to manage the layout.
-    $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default');
+    $this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display/default', ['layout[enabled]' => TRUE], 'Save');
+    $assert_session->linkExists('Manage layout');
     $this->clickLink('Manage layout');
 
     // Add a new block.
@@ -412,6 +421,7 @@ public function testDeletedView() {
 
     $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
     // Enable overrides.
+    $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save');
     $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
     $this->drupalGet('node/1');
 
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
index d5e0c7523c89..e4af3290c468 100644
--- a/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
@@ -41,6 +41,7 @@ protected function setUp() {
     ]);
 
     LayoutBuilderEntityViewDisplay::load('node.bundle_with_section_field.default')
+      ->enableLayoutBuilder()
       ->setOverridable()
       ->save();
 
diff --git a/core/modules/layout_builder/tests/src/Functional/Update/ExtraFieldUpdatePathTest.php b/core/modules/layout_builder/tests/src/Functional/Update/ExtraFieldUpdatePathTest.php
index f33ab299e135..43474c26b152 100644
--- a/core/modules/layout_builder/tests/src/Functional/Update/ExtraFieldUpdatePathTest.php
+++ b/core/modules/layout_builder/tests/src/Functional/Update/ExtraFieldUpdatePathTest.php
@@ -20,6 +20,7 @@ protected function setDatabaseDumpFiles() {
     $this->databaseDumpFiles = [
       __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz',
       __DIR__ . '/../../../fixtures/update/layout-builder.php',
+      __DIR__ . '/../../../fixtures/update/layout-builder-extra.php',
     ];
   }
 
@@ -27,15 +28,26 @@ protected function setDatabaseDumpFiles() {
    * Tests the upgrade path for Layout Builder extra fields.
    */
   public function testRunUpdates() {
+    // The default view mode has Layout Builder enabled.
+    $data = EntityViewDisplay::load('node.article.default')->toArray();
+    $this->assertArrayHasKey('third_party_settings', $data);
+    $this->assertArrayNotHasKey('sections', $data['third_party_settings']['layout_builder']);
+
+    // The teaser view mode does not have Layout Builder enabled.
     $data = EntityViewDisplay::load('node.article.teaser')->toArray();
     $this->assertArrayNotHasKey('third_party_settings', $data);
 
     $this->runUpdates();
 
-    $data = EntityViewDisplay::load('node.article.teaser')->toArray();
+    // The extra links have been added.
+    $data = EntityViewDisplay::load('node.article.default')->toArray();
     $components = $data['third_party_settings']['layout_builder']['sections'][0]->getComponents();
     $component = reset($components);
     $this->assertSame('extra_field_block:node:article:links', $component->getPluginId());
+
+    // No extra links have been added.
+    $data = EntityViewDisplay::load('node.article.teaser')->toArray();
+    $this->assertArrayNotHasKey('third_party_settings', $data);
   }
 
 }
diff --git a/core/modules/layout_builder/tests/src/Functional/Update/LayoutBuilderEnableUpdatePathTest.php b/core/modules/layout_builder/tests/src/Functional/Update/LayoutBuilderEnableUpdatePathTest.php
new file mode 100644
index 000000000000..5577539359dd
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/Functional/Update/LayoutBuilderEnableUpdatePathTest.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Drupal\Tests\layout_builder\Functional\Update;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+
+/**
+ * Tests the upgrade path for enabling Layout Builder.
+ *
+ * @see layout_builder_update_8601()
+ *
+ * @group layout_builder
+ * @group legacy
+ */
+class LayoutBuilderEnableUpdatePathTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz',
+      __DIR__ . '/../../../fixtures/update/layout-builder.php',
+      __DIR__ . '/../../../fixtures/update/layout-builder-enable.php',
+    ];
+  }
+
+  /**
+   * Tests the upgrade path for enabling Layout Builder.
+   */
+  public function testRunUpdates() {
+    $assert_session = $this->assertSession();
+
+    $expected = [
+      'sections' => [
+        [
+          'layout_id' => 'layout_onecol',
+          'layout_settings' => [],
+          'components' => [
+            'some-uuid' => [
+              'uuid' => 'some-uuid',
+              'region' => 'content',
+              'configuration' => [
+                'id' => 'system_powered_by_block',
+              ],
+              'additional' => [],
+              'weight' => 0,
+            ],
+          ],
+        ],
+      ],
+    ];
+    $this->assertLayoutBuilderSettings($expected, 'block_content', 'basic', 'default');
+    $this->assertLayoutBuilderSettings(NULL, 'node', 'page', 'default');
+
+    $this->runUpdates();
+
+    // The display with existing sections is enabled while the other is not.
+    $expected['enabled'] = TRUE;
+    $this->assertLayoutBuilderSettings($expected, 'block_content', 'basic', 'default');
+    $this->assertLayoutBuilderSettings(NULL, 'node', 'page', 'default');
+
+    $this->drupalLogin($this->rootUser);
+    $this->drupalGet('admin/structure/block/block-content/manage/basic/display');
+    $assert_session->checkboxChecked('layout[enabled]');
+    $this->drupalGet('admin/structure/types/manage/page/display');
+    $assert_session->checkboxNotChecked('layout[enabled]');
+  }
+
+  /**
+   * Asserts the Layout Builder settings for a given display.
+   *
+   * @param mixed $expected
+   *   The expected value.
+   * @param string $entity_type_id
+   *   The entity type ID.
+   * @param string $bundle
+   *   The bundle.
+   * @param string $view_mode
+   *   The view mode.
+   */
+  protected function assertLayoutBuilderSettings($expected, $entity_type_id, $bundle, $view_mode) {
+    $this->assertEquals($expected, \Drupal::config("core.entity_view_display.$entity_type_id.$bundle.$view_mode")->get('third_party_settings.layout_builder'));
+  }
+
+}
diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/AjaxBlockTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/AjaxBlockTest.php
index 1f7849ef9a50..653185fe8964 100644
--- a/core/modules/layout_builder/tests/src/FunctionalJavascript/AjaxBlockTest.php
+++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/AjaxBlockTest.php
@@ -60,7 +60,7 @@ public function testAddAjaxBlock() {
     $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
 
     // From the manage display page, go to manage the layout.
-    $this->drupalGet("$field_ui_prefix/display/default");
+    $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save');
     $assert_session->linkExists('Manage layout');
     $this->clickLink('Manage layout');
     $assert_session->addressEquals("$field_ui_prefix/display-layout/default");
diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/ItemLayoutFieldBlockTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/ItemLayoutFieldBlockTest.php
index 8f425e4e61e6..92b48a7762ae 100644
--- a/core/modules/layout_builder/tests/src/FunctionalJavascript/ItemLayoutFieldBlockTest.php
+++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/ItemLayoutFieldBlockTest.php
@@ -44,7 +44,10 @@ public function testAddAjaxBlock() {
     $page = $this->getSession()->getPage();
 
     // Allow overrides for the layout.
-    $this->drupalPostForm('admin/structure/types/manage/bundle_with_layout_overrides/display/default', ['layout[allow_custom]' => TRUE], 'Save');
+    $this->drupalGet('admin/structure/types/manage/bundle_with_layout_overrides/display/default');
+    $page->checkField('layout[enabled]');
+    $page->checkField('layout[allow_custom]');
+    $page->pressButton('Save');
 
     // Start by creating a node of type with layout overrides.
     $node = $this->createNode([
diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderOptInTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderOptInTest.php
new file mode 100644
index 000000000000..d75636564fec
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderOptInTest.php
@@ -0,0 +1,184 @@
+<?php
+
+namespace Drupal\Tests\layout_builder\FunctionalJavascript;
+
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+
+/**
+ * Tests the ability for opting in and out of Layout Builder.
+ *
+ * @group layout_builder
+ */
+class LayoutBuilderOptInTest extends WebDriverTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'node',
+    'field_ui',
+    'block',
+  ];
+
+  /**
+   * {@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');
+
+    // Create one content type before installing Layout Builder and one after.
+    $this->createContentType(['type' => 'before']);
+    $this->container->get('module_installer')->install(['layout_builder']);
+    $this->rebuildAll();
+    $this->createContentType(['type' => 'after']);
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'configure any layout',
+      'administer node display',
+    ]));
+  }
+
+  /**
+   * Tests the interaction between the two layout checkboxes.
+   */
+  public function testCheckboxLogic() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalGet('admin/structure/types/manage/before/display/default');
+    // Both fields are unchecked and allow_custom is disabled and hidden.
+    $assert_session->checkboxNotChecked('layout[enabled]');
+    $assert_session->checkboxNotChecked('layout[allow_custom]');
+    $assert_session->fieldDisabled('layout[allow_custom]');
+    $this->assertFalse($page->findField('layout[allow_custom]')->isVisible());
+
+    // Checking is_enable will show allow_custom.
+    $page->checkField('layout[enabled]');
+    $assert_session->checkboxNotChecked('layout[allow_custom]');
+    $this->assertTrue($page->findField('layout[allow_custom]')->isVisible());
+    $page->pressButton('Save');
+    $assert_session->checkboxChecked('layout[enabled]');
+    $assert_session->checkboxNotChecked('layout[allow_custom]');
+
+    // Check and submit allow_custom.
+    $page->checkField('layout[allow_custom]');
+    $page->pressButton('Save');
+    $assert_session->checkboxChecked('layout[enabled]');
+    $assert_session->checkboxChecked('layout[allow_custom]');
+
+    // Reset the checkboxes.
+    $page->uncheckField('layout[enabled]');
+    $page->pressButton('Save');
+    $page->pressButton('Confirm');
+    $assert_session->checkboxNotChecked('layout[enabled]');
+    $assert_session->checkboxNotChecked('layout[allow_custom]');
+
+    // Check both at the same time.
+    $page->checkField('layout[enabled]');
+    $page->checkField('layout[allow_custom]');
+    $page->pressButton('Save');
+    $assert_session->checkboxChecked('layout[enabled]');
+    $assert_session->checkboxChecked('layout[allow_custom]');
+  }
+
+  /**
+   * Tests the expected default values for enabling Layout Builder.
+   */
+  public function testDefaultValues() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    // Both the content type created before and after Layout Builder was
+    // installed is still using the Field UI.
+    $this->drupalGet('admin/structure/types/manage/before/display/default');
+    $assert_session->checkboxNotChecked('layout[enabled]');
+
+    $field_ui_prefix = 'admin/structure/types/manage/after/display/default';
+    $this->drupalGet($field_ui_prefix);
+    $assert_session->checkboxNotChecked('layout[enabled]');
+    $page->checkField('layout[enabled]');
+    $page->pressButton('Save');
+
+    $layout_builder_ui = $this->getPathForFieldBlock('node', 'after', 'default', 'body');
+
+    $assert_session->linkExists('Manage layout');
+    $this->clickLink('Manage layout');
+    // Ensure the body appears once and only once.
+    $assert_session->elementsCount('css', '.field--name-body', 1);
+
+    // Change the body formatter to Trimmed.
+    $this->drupalGet($layout_builder_ui);
+    $assert_session->fieldValueEquals('settings[formatter][type]', 'text_default');
+    $page->selectFieldOption('settings[formatter][type]', 'text_trimmed');
+    $assert_session->assertWaitOnAjaxRequest();
+    $page->pressButton('Update');
+    $assert_session->linkExists('Save Layout');
+    $this->clickLink('Save Layout');
+
+    $this->drupalGet($layout_builder_ui);
+    $assert_session->fieldValueEquals('settings[formatter][type]', 'text_trimmed');
+
+    // Disable Layout Builder.
+    $this->drupalPostForm($field_ui_prefix, ['layout[enabled]' => FALSE], 'Save');
+    $page->pressButton('Confirm');
+
+    // The Layout Builder UI is no longer accessible.
+    $this->drupalGet($layout_builder_ui);
+    $assert_session->pageTextContains('You are not authorized to access this page.');
+
+    // The original body formatter is reflected in Field UI.
+    $this->drupalGet($field_ui_prefix);
+    $assert_session->fieldValueEquals('fields[body][type]', 'text_default');
+
+    // Change the body formatter to Summary.
+    $page->selectFieldOption('fields[body][type]', 'text_summary_or_trimmed');
+    $assert_session->assertWaitOnAjaxRequest();
+    $page->pressButton('Save');
+    $assert_session->fieldValueEquals('fields[body][type]', 'text_summary_or_trimmed');
+
+    // Reactivate Layout Builder.
+    $this->drupalPostForm($field_ui_prefix, ['layout[enabled]' => TRUE], 'Save');
+    $assert_session->linkExists('Manage layout');
+    $this->clickLink('Manage layout');
+    // Ensure the body appears once and only once.
+    $assert_session->elementsCount('css', '.field--name-body', 1);
+
+    // The changed body formatter is reflected in Layout Builder UI.
+    $this->drupalGet($this->getPathForFieldBlock('node', 'after', 'default', 'body'));
+    $assert_session->fieldValueEquals('settings[formatter][type]', 'text_summary_or_trimmed');
+  }
+
+  /**
+   * Returns the path to update a field block in the UI.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   * @param string $bundle
+   *   The bundle.
+   * @param string $view_mode
+   *   The view mode.
+   * @param string $field_name
+   *   The field name.
+   *
+   * @return string
+   *   The path.
+   */
+  protected function getPathForFieldBlock($entity_type_id, $bundle, $view_mode, $field_name) {
+    $delta = 0;
+    /** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface $display */
+    $display = $this->container->get('entity_type.manager')->getStorage('entity_view_display')->load("$entity_type_id.$bundle.$view_mode");
+    $body_component = NULL;
+    foreach ($display->getSection($delta)->getComponents() as $component) {
+      if ($component->getPluginId() === "field_block:$entity_type_id:$bundle:$field_name") {
+        $body_component = $component;
+      }
+    }
+    $this->assertNotNull($body_component);
+    return 'layout_builder/update/block/defaults/node.after.default/0/content/' . $body_component->getUuid();
+  }
+
+}
diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php
index d5a4cd0b8a8f..d924c688b40b 100644
--- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php
+++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php
@@ -23,7 +23,7 @@ abstract class LayoutBuilderCompatibilityTestBase extends EntityKernelTestBase {
   /**
    * The entity view display.
    *
-   * @var \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface
+   * @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface
    */
   protected $display;
 
@@ -86,13 +86,21 @@ protected function setUp() {
   /**
    * Installs the Layout Builder.
    *
-   * Also configures and reloads the entity display, and reloads the entity.
+   * Also configures and reloads the entity display.
    */
   protected function installLayoutBuilder() {
     $this->container->get('module_installer')->install(['layout_builder']);
     $this->refreshServices();
 
     $this->display = $this->reloadEntity($this->display);
+    $this->display->enableLayoutBuilder()->save();
+    $this->entity = $this->reloadEntity($this->entity);
+  }
+
+  /**
+   * Enables overrides for the display and reloads the entity.
+   */
+  protected function enableOverrides() {
     $this->display->setOverridable()->save();
     $this->entity = $this->reloadEntity($this->entity);
   }
diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderFieldLayoutCompatibilityTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderFieldLayoutCompatibilityTest.php
index 59a76615c188..57dacb5b18ce 100644
--- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderFieldLayoutCompatibilityTest.php
+++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderFieldLayoutCompatibilityTest.php
@@ -54,6 +54,7 @@ public function testCompatibility() {
     $this->assertFieldAttributes($this->entity, $expected_fields);
 
     // Add a layout override.
+    $this->enableOverrides();
     /** @var \Drupal\layout_builder\SectionStorageInterface $field_list */
     $field_list = $this->entity->get('layout_builder__layout');
     $field_list->appendSection(new Section('layout_onecol'));
diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php
index fa646df1c91b..aa1b9942c853 100644
--- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php
+++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php
@@ -33,6 +33,8 @@ public function testCompatibility() {
     $this->assertFieldAttributes($this->entity, $expected_fields);
 
     // Add a layout override.
+    $this->enableOverrides();
+    $this->entity = $this->reloadEntity($this->entity);
     $this->entity->get('layout_builder__layout')->appendSection(new Section('layout_onecol'));
     $this->entity->save();
 
diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutSectionItemListTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutSectionItemListTest.php
index af8395f1731a..5bd354675cb8 100644
--- a/core/modules/layout_builder/tests/src/Kernel/LayoutSectionItemListTest.php
+++ b/core/modules/layout_builder/tests/src/Kernel/LayoutSectionItemListTest.php
@@ -32,7 +32,10 @@ protected function getSectionStorage(array $section_data) {
       'bundle' => 'entity_test_base_field_display',
       'mode' => 'default',
       'status' => TRUE,
-    ])->setOverridable()->save();
+    ])
+      ->enableLayoutBuilder()
+      ->setOverridable()
+      ->save();
 
     array_map(function ($row) {
       return ['section' => $row];
diff --git a/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php b/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php
index 7e5304dbbea4..d30917a44635 100644
--- a/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php
+++ b/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php
@@ -254,6 +254,7 @@ public function testBuildRoutes() {
         [
           '_field_ui_view_mode_access' => 'administer with_bundle_key display',
           '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
         ],
         [
           'parameters' => [
@@ -275,6 +276,7 @@ public function testBuildRoutes() {
         [
           '_field_ui_view_mode_access' => 'administer with_bundle_key display',
           '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
         ],
         [
           'parameters' => [
@@ -296,6 +298,7 @@ public function testBuildRoutes() {
         [
           '_field_ui_view_mode_access' => 'administer with_bundle_key display',
           '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
         ],
         [
           'parameters' => [
@@ -305,6 +308,26 @@ public function testBuildRoutes() {
           '_admin_route' => FALSE,
         ]
       ),
+      'layout_builder.defaults.with_bundle_key.disable' => new Route(
+        '/admin/entity/whatever/display-layout/{view_mode_name}/disable',
+        [
+          'entity_type_id' => 'with_bundle_key',
+          'bundle_key' => 'my_bundle_type',
+          'section_storage_type' => 'defaults',
+          'section_storage' => '',
+          '_form' => '\Drupal\layout_builder\Form\LayoutBuilderDisableForm',
+        ],
+        [
+          '_field_ui_view_mode_access' => 'administer with_bundle_key display',
+          '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
+        ],
+        [
+          'parameters' => [
+            'section_storage' => ['layout_builder_tempstore' => TRUE],
+          ],
+        ]
+      ),
       'layout_builder.defaults.with_bundle_parameter.view' => new Route(
         '/admin/entity/{bundle}/display-layout/{view_mode_name}',
         [
@@ -318,6 +341,7 @@ public function testBuildRoutes() {
         [
           '_field_ui_view_mode_access' => 'administer with_bundle_parameter display',
           '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
         ],
         [
           'parameters' => [
@@ -338,6 +362,7 @@ public function testBuildRoutes() {
         [
           '_field_ui_view_mode_access' => 'administer with_bundle_parameter display',
           '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
         ],
         [
           'parameters' => [
@@ -358,6 +383,7 @@ public function testBuildRoutes() {
         [
           '_field_ui_view_mode_access' => 'administer with_bundle_parameter display',
           '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
         ],
         [
           'parameters' => [
@@ -367,6 +393,25 @@ public function testBuildRoutes() {
           '_admin_route' => FALSE,
         ]
       ),
+      'layout_builder.defaults.with_bundle_parameter.disable' => new Route(
+        '/admin/entity/{bundle}/display-layout/{view_mode_name}/disable',
+        [
+          'entity_type_id' => 'with_bundle_parameter',
+          'section_storage_type' => 'defaults',
+          'section_storage' => '',
+          '_form' => '\Drupal\layout_builder\Form\LayoutBuilderDisableForm',
+        ],
+        [
+          '_field_ui_view_mode_access' => 'administer with_bundle_parameter display',
+          '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
+        ],
+        [
+          'parameters' => [
+            'section_storage' => ['layout_builder_tempstore' => TRUE],
+          ],
+        ]
+      ),
     ];
 
     $collection = new RouteCollection();
diff --git a/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php b/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php
index bbeb049a6172..110ec2021e65 100644
--- a/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php
+++ b/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php
@@ -223,6 +223,7 @@ public function testBuildRoutes() {
         ],
         [
           '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
         ],
         [
           'parameters' => [
@@ -242,6 +243,7 @@ public function testBuildRoutes() {
         ],
         [
           '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
         ],
         [
           'parameters' => [
@@ -261,6 +263,7 @@ public function testBuildRoutes() {
         ],
         [
           '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
         ],
         [
           'parameters' => [
@@ -280,6 +283,7 @@ public function testBuildRoutes() {
         ],
         [
           '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
         ],
         [
           'parameters' => [
@@ -301,6 +305,7 @@ public function testBuildRoutes() {
         ],
         [
           '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
           'with_integer_id' => '\d+',
         ],
         [
@@ -321,6 +326,7 @@ public function testBuildRoutes() {
         ],
         [
           '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
           'with_integer_id' => '\d+',
         ],
         [
@@ -341,6 +347,7 @@ public function testBuildRoutes() {
         ],
         [
           '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
           'with_integer_id' => '\d+',
         ],
         [
@@ -361,6 +368,7 @@ public function testBuildRoutes() {
         ],
         [
           '_has_layout_section' => 'true',
+          '_layout_builder_access' => 'view',
           'with_integer_id' => '\d+',
         ],
         [
-- 
GitLab