diff --git a/core/modules/settings_tray/settings_tray.links.contextual.yml b/core/modules/settings_tray/settings_tray.links.contextual.yml
index 5534ab2..c62fa98 100644
--- a/core/modules/settings_tray/settings_tray.links.contextual.yml
+++ b/core/modules/settings_tray/settings_tray.links.contextual.yml
@@ -1,6 +1,6 @@
 settings_tray.block_configure:
   title: 'Quick edit'
-  route_name: 'entity.block.off_canvas_form'
+  route_name: 'entity.block.settings_tray_form'
   group: 'block'
   options:
     attributes:
diff --git a/core/modules/settings_tray/settings_tray.module b/core/modules/settings_tray/settings_tray.module
index 87b40ac..7a7d825 100644
--- a/core/modules/settings_tray/settings_tray.module
+++ b/core/modules/settings_tray/settings_tray.module
@@ -8,7 +8,7 @@
 use Drupal\Core\Asset\AttachedAssetsInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
-use Drupal\settings_tray\Block\BlockEntityOffCanvasForm;
+use Drupal\settings_tray\Block\BlockEntitySettingTrayForm;
 use Drupal\block\entity\Block;
 use Drupal\block\BlockInterface;
 
@@ -19,12 +19,12 @@ function settings_tray_help($route_name, RouteMatchInterface $route_match) {
   switch ($route_name) {
     case 'help.page.settings_tray':
       $output = '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('The Settings Tray module allows users with the <a href=":administer_block_permission">Administer blocks</a> and <a href=":contextual_permission">Use contextual links</a> permissions to edit blocks without visiting a separate page. For more information, see the <a href=":handbook_url">online documentation for the Settings Tray module</a>.', [':handbook_url' => 'https://www.drupal.org/documentation/modules/settings_tray', ':administer_block_permission' => \Drupal::url('user.admin_permissions', [], ['fragment' => 'module-block']), ':contextual_permission' => \Drupal::url('user.admin_permissions', [], ['fragment' => 'module-contextual'])]) . '</p>';
+      $output .= '<p>' . t('The Settings Tray module allows users with the <a href=":administer_block_permission">Administer blocks</a> and <a href=":contextual_permission">Use contextual links</a> permissions to edit blocks without visiting a separate page. For more information, see the <a href=":handbook_url">online documentation for the Settings Tray module</a>.', [':handbook_url' => 'https://www.drupal.org/documentation/modules/settings_tray', ':administer_block_permission' => Url::fromRoute('user.admin_permissions', [], ['fragment' => 'module-block']), ':contextual_permission' => Url::fromRoute('user.admin_permissions', [], ['fragment' => 'module-contextual'])]) . '</p>';
       $output .= '<h3>' . t('Uses') . '</h3>';
       $output .= '<dl>';
       $output .= '<dt>' . t('Editing blocks in place') . '</dt>';
       $output .= '<dd>';
-      $output .= '<p>' . t('To edit blocks in place, either click the <strong>Edit</strong> button in the toolbar and then click on the block, or choose "Quick edit" from the block\'s contextual link. (See the <a href=":contextual">Contextual Links module help</a> for more information about how to use contextual links.)', [':contextual' => \Drupal::url('help.page', ['name' => 'contextual'])]) . '</p>';
+      $output .= '<p>' . t('To edit blocks in place, either click the <strong>Edit</strong> button in the toolbar and then click on the block, or choose "Quick edit" from the block\'s contextual link. (See the <a href=":contextual">Contextual Links module help</a> for more information about how to use contextual links.)', [':contextual' => Url::fromRoute('help.page', ['name' => 'contextual'])]) . '</p>';
       $output .= '<p>' . t('The Settings Tray for the block will open in a sidebar, with a compact form for configuring what the block shows.') . '</p>';
       $output .= '<p>' . t('Save the form and the changes will be immediately visible on the page.') . '</p>';
       $output .= '</dd>';
@@ -96,8 +96,8 @@ function settings_tray_block_view_alter(array &$build) {
 function settings_tray_entity_type_build(array &$entity_types) {
   /* @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
   $entity_types['block']
-    ->setFormClass('off_canvas', BlockEntityOffCanvasForm::class)
-    ->setLinkTemplate('off_canvas-form', '/admin/structure/block/manage/{block}/off-canvas');
+    ->setFormClass('settings_tray', BlockEntitySettingTrayForm::class)
+    ->setLinkTemplate('settings_tray-form', '/admin/structure/block/manage/{block}/settings-tray');
 }
 
 /**
diff --git a/core/modules/settings_tray/settings_tray.routing.yml b/core/modules/settings_tray/settings_tray.routing.yml
index f8e2bfe..370fc7f 100644
--- a/core/modules/settings_tray/settings_tray.routing.yml
+++ b/core/modules/settings_tray/settings_tray.routing.yml
@@ -1,9 +1,16 @@
-entity.block.off_canvas_form:
-  path: '/admin/structure/block/manage/{block}/off-canvas'
+entity.block.settings_tray_form:
+  path: '/admin/structure/block/manage/{block}/settings-tray'
   defaults:
-    _entity_form: 'block.off_canvas'
-    _title_callback: '\Drupal\settings_tray\Block\BlockEntityOffCanvasForm::title'
+    _entity_form: 'block.settings_tray'
+    _title_callback: '\Drupal\settings_tray\Block\BlockEntitySettingTrayForm::title'
   requirements:
     _permission: 'administer blocks'
     _access_block_plugin_has_settings_tray_form: 'TRUE'
     _access_block_has_overrides_settings_tray_form: 'TRUE'
+
+# Deprecated.
+# @see entity.block.settings_tray_form
+# @see \Drupal\settings_tray\RouteProcessor\BlockEntityOffCanvasFormRouteProcessorBC
+# @todo Remove in Drupal 9.0.0.
+entity.block.off_canvas_form:
+  path: ''
diff --git a/core/modules/settings_tray/settings_tray.services.yml b/core/modules/settings_tray/settings_tray.services.yml
index 7c57e95..9f61546 100644
--- a/core/modules/settings_tray/settings_tray.services.yml
+++ b/core/modules/settings_tray/settings_tray.services.yml
@@ -7,3 +7,12 @@ services:
     class: Drupal\settings_tray\Access\BlockPluginHasSettingsTrayFormAccessCheck
     tags:
       - { name: access_check, applies_to: _access_block_plugin_has_settings_tray_form }
+
+  # BC layers.
+  # @todo Remove in Drupal 9.0.0.
+  settings_tray.route_processor_off_canvas_form_bc:
+    class: \Drupal\settings_tray\RouteProcessor\BlockEntityOffCanvasFormRouteProcessorBC
+    arguments: ['@router.route_provider']
+    public: false
+    tags:
+      - { name: route_processor_outbound }
diff --git a/core/modules/settings_tray/src/Block/BlockEntityOffCanvasForm.php b/core/modules/settings_tray/src/Block/BlockEntityOffCanvasForm.php
deleted file mode 100644
index 2c6f80d..0000000
--- a/core/modules/settings_tray/src/Block/BlockEntityOffCanvasForm.php
+++ /dev/null
@@ -1,194 +0,0 @@
-<?php
-
-namespace Drupal\settings_tray\Block;
-
-use Drupal\block\BlockForm;
-use Drupal\block\BlockInterface;
-use Drupal\Component\Utility\Html;
-use Drupal\Core\Ajax\AjaxResponse;
-use Drupal\Core\Ajax\RedirectCommand;
-use Drupal\Core\Ajax\ReplaceCommand;
-use Drupal\Core\Block\BlockPluginInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Plugin\PluginWithFormsInterface;
-use Drupal\Core\Url;
-
-/**
- * Provides form for block instance forms when used in the off-canvas dialog.
- *
- * This form removes advanced sections of regular block form such as the
- * visibility settings, machine ID and region.
- *
- * @internal
- */
-class BlockEntityOffCanvasForm extends BlockForm {
-
-  /**
-   * Provides a title callback to get the block's admin label.
-   *
-   * @param \Drupal\block\BlockInterface $block
-   *   The block entity.
-   *
-   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
-   *   The title.
-   */
-  public function title(BlockInterface $block) {
-    // @todo Wrap "Configure " in <span class="visually-hidden"></span> once
-    //   https://www.drupal.org/node/2359901 is fixed.
-    return $this->t('Configure @block', ['@block' => $block->getPlugin()->getPluginDefinition()['admin_label']]);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function form(array $form, FormStateInterface $form_state) {
-    $form = parent::form($form, $form_state);
-
-    // Create link to full block form.
-    $query = [];
-    if ($destination = $this->getRequest()->query->get('destination')) {
-      $query['destination'] = $destination;
-    }
-    $form['advanced_link'] = [
-      '#type' => 'link',
-      '#title' => $this->t('Advanced block options'),
-      '#url' => $this->entity->toUrl('edit-form', ['query' => $query]),
-      '#weight' => 1000,
-    ];
-
-    // Remove the ID and region elements.
-    unset($form['id'], $form['region'], $form['settings']['admin_label']);
-
-    if (isset($form['settings']['label_display']) && isset($form['settings']['label'])) {
-      // Only show the label input if the label will be shown on the page.
-      $form['settings']['label_display']['#weight'] = -100;
-      $form['settings']['label']['#states']['visible'] = [
-        ':input[name="settings[label_display]"]' => ['checked' => TRUE],
-      ];
-
-      // Relabel to "Block title" because on the front-end this may be confused
-      // with page title.
-      $form['settings']['label']['#title'] = $this->t("Block title");
-      $form['settings']['label_display']['#title'] = $this->t("Display block title");
-    }
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function actions(array $form, FormStateInterface $form_state) {
-    $actions = parent::actions($form, $form_state);
-    $actions['submit']['#value'] = $this->t('Save @block', ['@block' => $this->entity->getPlugin()->getPluginDefinition()['admin_label']]);
-    $actions['delete']['#access'] = FALSE;
-    return $actions;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function buildVisibilityInterface(array $form, FormStateInterface $form_state) {
-    // Do not display the visibility.
-    return [];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function validateVisibility(array $form, FormStateInterface $form_state) {
-    // Intentionally empty.
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function submitVisibility(array $form, FormStateInterface $form_state) {
-    // Intentionally empty.
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function getPluginForm(BlockPluginInterface $block) {
-    if ($block instanceof PluginWithFormsInterface) {
-      return $this->pluginFormFactory->createInstance($block, 'settings_tray', 'configure');
-    }
-    return $block;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state) {
-    $form = parent::buildForm($form, $form_state);
-    $form['actions']['submit']['#ajax'] = [
-      'callback' => '::submitFormDialog',
-    ];
-    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
-
-    // static::submitFormDialog() requires data-drupal-selector to be the same
-    // between the various Ajax requests. A bug in
-    // \Drupal\Core\Form\FormBuilder prevents that from happening unless
-    // $form['#id'] is also the same. Normally, #id is set to a unique HTML ID
-    // via Html::getUniqueId(), but here we bypass that in order to work around
-    // the data-drupal-selector bug. This is okay so long as we assume that this
-    // form only ever occurs once on a page.
-    // @todo Remove this workaround once https://www.drupal.org/node/2897377 is
-    //   fixed.
-    $form['#id'] = Html::getId($form_state->getBuildInfo()['form_id']);
-
-    return $form;
-  }
-
-  /**
-   * Submit form dialog #ajax callback.
-   *
-   * @param array $form
-   *   An associative array containing the structure of the form.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   *
-   * @return \Drupal\Core\Ajax\AjaxResponse
-   *   An AJAX response that display validation error messages or redirects
-   *   to a URL
-   *
-   * @todo Repalce this callback with generic trait in
-   *   https://www.drupal.org/node/2896535.
-   */
-  public function submitFormDialog(array &$form, FormStateInterface $form_state) {
-    $response = new AjaxResponse();
-    if ($form_state->hasAnyErrors()) {
-      $form['status_messages'] = [
-        '#type' => 'status_messages',
-        '#weight' => -1000,
-      ];
-      $command = new ReplaceCommand('[data-drupal-selector="' . $form['#attributes']['data-drupal-selector'] . '"]', $form);
-    }
-    else {
-      if ($redirect_url = $this->getRedirectUrl()) {
-        $command = new RedirectCommand($redirect_url->setAbsolute()->toString());
-      }
-      else {
-        // Settings Tray always provides a destination.
-        throw new \Exception("No destination provided by Settings Tray form");
-      }
-    }
-    return $response->addCommand($command);
-  }
-
-  /**
-   * Gets the form's redirect URL from 'destination' provide in the request.
-   *
-   * @return \Drupal\Core\Url|null
-   *   The redirect URL or NULL if dialog should just be closed.
-   */
-  protected function getRedirectUrl() {
-    // \Drupal\Core\Routing\RedirectDestination::get() cannot be used directly
-    // because it will use <current> if 'destination' is not in the query
-    // string.
-    if ($this->getRequest()->query->has('destination') && $destination = $this->getRedirectDestination()->get()) {
-      return Url::fromUserInput('/' . $destination);
-    }
-  }
-
-}
diff --git a/core/modules/settings_tray/src/Block/BlockEntitySettingTrayForm.php b/core/modules/settings_tray/src/Block/BlockEntitySettingTrayForm.php
new file mode 100644
index 0000000..fd44b0c
--- /dev/null
+++ b/core/modules/settings_tray/src/Block/BlockEntitySettingTrayForm.php
@@ -0,0 +1,194 @@
+<?php
+
+namespace Drupal\settings_tray\Block;
+
+use Drupal\block\BlockForm;
+use Drupal\block\BlockInterface;
+use Drupal\Component\Utility\Html;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\RedirectCommand;
+use Drupal\Core\Ajax\ReplaceCommand;
+use Drupal\Core\Block\BlockPluginInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\PluginWithFormsInterface;
+use Drupal\Core\Url;
+
+/**
+ * Provides form for block instance forms when used in the off-canvas dialog.
+ *
+ * This form removes advanced sections of regular block form such as the
+ * visibility settings, machine ID and region.
+ *
+ * @internal
+ */
+class BlockEntitySettingTrayForm extends BlockForm {
+
+  /**
+   * Provides a title callback to get the block's admin label.
+   *
+   * @param \Drupal\block\BlockInterface $block
+   *   The block entity.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
+   *   The title.
+   */
+  public function title(BlockInterface $block) {
+    // @todo Wrap "Configure " in <span class="visually-hidden"></span> once
+    //   https://www.drupal.org/node/2359901 is fixed.
+    return $this->t('Configure @block', ['@block' => $block->getPlugin()->getPluginDefinition()['admin_label']]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, FormStateInterface $form_state) {
+    $form = parent::form($form, $form_state);
+
+    // Create link to full block form.
+    $query = [];
+    if ($destination = $this->getRequest()->query->get('destination')) {
+      $query['destination'] = $destination;
+    }
+    $form['advanced_link'] = [
+      '#type' => 'link',
+      '#title' => $this->t('Advanced block options'),
+      '#url' => $this->entity->toUrl('edit-form', ['query' => $query]),
+      '#weight' => 1000,
+    ];
+
+    // Remove the ID and region elements.
+    unset($form['id'], $form['region'], $form['settings']['admin_label']);
+
+    if (isset($form['settings']['label_display']) && isset($form['settings']['label'])) {
+      // Only show the label input if the label will be shown on the page.
+      $form['settings']['label_display']['#weight'] = -100;
+      $form['settings']['label']['#states']['visible'] = [
+        ':input[name="settings[label_display]"]' => ['checked' => TRUE],
+      ];
+
+      // Relabel to "Block title" because on the front-end this may be confused
+      // with page title.
+      $form['settings']['label']['#title'] = $this->t("Block title");
+      $form['settings']['label_display']['#title'] = $this->t("Display block title");
+    }
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function actions(array $form, FormStateInterface $form_state) {
+    $actions = parent::actions($form, $form_state);
+    $actions['submit']['#value'] = $this->t('Save @block', ['@block' => $this->entity->getPlugin()->getPluginDefinition()['admin_label']]);
+    $actions['delete']['#access'] = FALSE;
+    return $actions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function buildVisibilityInterface(array $form, FormStateInterface $form_state) {
+    // Do not display the visibility.
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function validateVisibility(array $form, FormStateInterface $form_state) {
+    // Intentionally empty.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function submitVisibility(array $form, FormStateInterface $form_state) {
+    // Intentionally empty.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getPluginForm(BlockPluginInterface $block) {
+    if ($block instanceof PluginWithFormsInterface) {
+      return $this->pluginFormFactory->createInstance($block, 'settings_tray', 'configure');
+    }
+    return $block;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form = parent::buildForm($form, $form_state);
+    $form['actions']['submit']['#ajax'] = [
+      'callback' => '::submitFormDialog',
+    ];
+    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
+
+    // static::submitFormDialog() requires data-drupal-selector to be the same
+    // between the various Ajax requests. A bug in
+    // \Drupal\Core\Form\FormBuilder prevents that from happening unless
+    // $form['#id'] is also the same. Normally, #id is set to a unique HTML ID
+    // via Html::getUniqueId(), but here we bypass that in order to work around
+    // the data-drupal-selector bug. This is okay so long as we assume that this
+    // form only ever occurs once on a page.
+    // @todo Remove this workaround once https://www.drupal.org/node/2897377 is
+    //   fixed.
+    $form['#id'] = Html::getId($form_state->getBuildInfo()['form_id']);
+
+    return $form;
+  }
+
+  /**
+   * Submit form dialog #ajax callback.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @return \Drupal\Core\Ajax\AjaxResponse
+   *   An AJAX response that display validation error messages or redirects
+   *   to a URL
+   *
+   * @todo Repalce this callback with generic trait in
+   *   https://www.drupal.org/node/2896535.
+   */
+  public function submitFormDialog(array &$form, FormStateInterface $form_state) {
+    $response = new AjaxResponse();
+    if ($form_state->hasAnyErrors()) {
+      $form['status_messages'] = [
+        '#type' => 'status_messages',
+        '#weight' => -1000,
+      ];
+      $command = new ReplaceCommand('[data-drupal-selector="' . $form['#attributes']['data-drupal-selector'] . '"]', $form);
+    }
+    else {
+      if ($redirect_url = $this->getRedirectUrl()) {
+        $command = new RedirectCommand($redirect_url->setAbsolute()->toString());
+      }
+      else {
+        // Settings Tray always provides a destination.
+        throw new \Exception("No destination provided by Settings Tray form");
+      }
+    }
+    return $response->addCommand($command);
+  }
+
+  /**
+   * Gets the form's redirect URL from 'destination' provide in the request.
+   *
+   * @return \Drupal\Core\Url|null
+   *   The redirect URL or NULL if dialog should just be closed.
+   */
+  protected function getRedirectUrl() {
+    // \Drupal\Core\Routing\RedirectDestination::get() cannot be used directly
+    // because it will use <current> if 'destination' is not in the query
+    // string.
+    if ($this->getRequest()->query->has('destination') && $destination = $this->getRedirectDestination()->get()) {
+      return Url::fromUserInput('/' . $destination);
+    }
+  }
+
+}
diff --git a/core/modules/settings_tray/src/RouteProcessor/BlockEntityOffCanvasFormRouteProcessorBC.php b/core/modules/settings_tray/src/RouteProcessor/BlockEntityOffCanvasFormRouteProcessorBC.php
new file mode 100644
index 0000000..48935ae
--- /dev/null
+++ b/core/modules/settings_tray/src/RouteProcessor/BlockEntityOffCanvasFormRouteProcessorBC.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Drupal\settings_tray\RouteProcessor;
+
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
+use Drupal\Core\Routing\RouteProviderInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Processes the Block entity off-canvas form BC route.
+ *
+ * @internal
+ */
+class BlockEntityOffCanvasFormRouteProcessorBC implements OutboundRouteProcessorInterface {
+
+  /**
+   * The route provider.
+   *
+   * @var \Drupal\Core\Routing\RouteProviderInterface
+   */
+  protected $routeProvider;
+
+  /**
+   * Constructs a BlockEntityOffCanvasFormRouteProcessorBC object.
+   *
+   * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
+   *   The route provider.
+   */
+  public function __construct(RouteProviderInterface $route_provider) {
+    $this->routeProvider = $route_provider;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processOutbound($route_name, Route $route, array &$parameters, BubbleableMetadata $bubbleable_metadata = NULL) {
+    if ($route_name === 'entity.block.off_canvas_form') {
+      $redirected_route_name = 'entity.block.settings_tray_form';
+      @trigger_error(sprintf("The '%s' route is deprecated since version 8.5.x and will be removed in 9.0.0. Use the '%s' route instead.", $route_name, $redirected_route_name), E_USER_DEPRECATED);
+      static::overwriteRoute($route, $this->routeProvider->getRouteByName($redirected_route_name));
+    }
+  }
+
+  /**
+   * Overwrites one route's metadata with the other's.
+   *
+   * @param \Symfony\Component\Routing\Route $target_route
+   *   The route whose metadata to overwrite.
+   * @param \Symfony\Component\Routing\Route $source_route
+   *   The route whose metadata to read from.
+   *
+   * @see \Symfony\Component\Routing\Route
+   */
+  protected static function overwriteRoute(Route $target_route, Route $source_route) {
+    $target_route->setPath($source_route->getPath());
+    $target_route->setDefaults($source_route->getDefaults());
+    $target_route->setRequirements($source_route->getRequirements());
+    $target_route->setOptions($source_route->getOptions());
+    $target_route->setHost($source_route->getHost());
+    $target_route->setSchemes($source_route->getSchemes());
+    $target_route->setMethods($source_route->getMethods());
+  }
+
+}
diff --git a/core/modules/settings_tray/tests/src/Functional/BcRoutesTest.php b/core/modules/settings_tray/tests/src/Functional/BcRoutesTest.php
new file mode 100644
index 0000000..6750b1c
--- /dev/null
+++ b/core/modules/settings_tray/tests/src/Functional/BcRoutesTest.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\Tests\settings_tray\Functional;
+
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests Settings Tray BC routes.
+ *
+ * @group settings_tray
+ * @group legacy
+ */
+class BcRoutesTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'settings_tray',
+  ];
+
+  /**
+   * @expectedDeprecation The 'entity.block.off_canvas_form' route is deprecated since version 8.5.x and will be removed in 9.0.0. Use the 'entity.block.settings_tray_form' route instead.
+   */
+  public function testOffCanvasFormRouteBc() {
+    $block = $this->placeBlock('system_powered_by_block');
+    $url_for_current_route = Url::fromRoute('entity.block.settings_tray_form', ['block' => $block->id()])->toString(TRUE)->getGeneratedUrl();
+    $url_for_bc_route = Url::fromRoute('entity.block.off_canvas_form', ['block' => $block->id()])->toString(TRUE)->getGeneratedUrl();
+    $this->assertSame($url_for_current_route, $url_for_bc_route);
+  }
+
+}
diff --git a/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php
index c677d08..7475dab 100644
--- a/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php
+++ b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php
@@ -84,7 +84,7 @@ public function testBlocks($theme, $block_plugin, $new_page_text, $element_selec
 
     $link = $page->find('css', "$block_selector .contextual-links li a");
     $this->assertEquals('Quick edit', $link->getText(), "'Quick edit' is the first contextual link for the block.");
-    $this->assertContains("/admin/structure/block/manage/$block_id/off-canvas?destination=user/2", $link->getAttribute('href'));
+    $this->assertContains("/admin/structure/block/manage/$block_id/settings-tray?destination=user/2", $link->getAttribute('href'));
 
     if (isset($toolbar_item)) {
       // Check that you can open a toolbar tray and it will be closed after
@@ -525,7 +525,7 @@ public function testCustomBlockLinks() {
     $href = array_search('Quick edit', $link_labels);
     $this->assertEquals('', $href);
     $href = array_search('Quick edit settings', $link_labels);
-    $this->assertTrue(strstr($href, '/admin/structure/block/manage/custom/off-canvas?destination=user/2') !== FALSE);
+    $this->assertTrue(strstr($href, '/admin/structure/block/manage/custom/settings-tray?destination=user/2') !== FALSE);
   }
 
   /**
diff --git a/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php.orig b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php.orig
new file mode 100644
index 0000000..c677d08
--- /dev/null
+++ b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php.orig
@@ -0,0 +1,720 @@
+<?php
+
+namespace Drupal\Tests\settings_tray\FunctionalJavascript;
+
+use Drupal\block\Entity\Block;
+use Drupal\block_content\Entity\BlockContent;
+use Drupal\block_content\Entity\BlockContentType;
+use Drupal\menu_link_content\Entity\MenuLinkContent;
+use Drupal\settings_tray_test\Plugin\Block\SettingsTrayFormAnnotationIsClassBlock;
+use Drupal\settings_tray_test\Plugin\Block\SettingsTrayFormAnnotationNoneBlock;
+use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait;
+use Drupal\Tests\system\FunctionalJavascript\OffCanvasTestBase;
+use Drupal\user\Entity\Role;
+use Drupal\user\RoleInterface;
+
+/**
+ * Testing opening and saving block forms in the off-canvas dialog.
+ *
+ * @group settings_tray
+ */
+class SettingsTrayBlockFormTest extends OffCanvasTestBase {
+
+  use ContextualLinkClickTrait;
+
+  const TOOLBAR_EDIT_LINK_SELECTOR = '#toolbar-bar div.contextual-toolbar-tab button';
+
+  const LABEL_INPUT_SELECTOR = 'input[data-drupal-selector="edit-settings-label"]';
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'node',
+    'block',
+    'system',
+    'breakpoint',
+    'toolbar',
+    'contextual',
+    'settings_tray',
+    'search',
+    'block_content',
+    'settings_tray_test',
+    // Add test module to override CSS pointer-events properties because they
+    // cause test failures.
+    'settings_tray_test_css',
+    'settings_tray_test',
+    'settings_tray_override_test',
+    'menu_ui',
+    'menu_link_content',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->createBlockContentType('basic', TRUE);
+    $block_content = $this->createBlockContent('Custom Block', 'basic', TRUE);
+    $user = $this->createUser([
+      'administer blocks',
+      'access contextual links',
+      'access toolbar',
+      'administer nodes',
+      'search content',
+    ]);
+    $this->drupalLogin($user);
+    $this->placeBlock('block_content:' . $block_content->uuid(), ['id' => 'custom']);
+  }
+
+  /**
+   * Tests opening off-canvas dialog by click blocks and elements in the blocks.
+   *
+   * @dataProvider providerTestBlocks
+   */
+  public function testBlocks($theme, $block_plugin, $new_page_text, $element_selector, $label_selector, $button_text, $toolbar_item) {
+    $web_assert = $this->assertSession();
+    $page = $this->getSession()->getPage();
+    $this->enableTheme($theme);
+    $block = $this->placeBlock($block_plugin);
+    $block_selector = $this->getBlockSelector($block);
+    $block_id = $block->id();
+    $this->drupalGet('user');
+
+    $link = $page->find('css', "$block_selector .contextual-links li a");
+    $this->assertEquals('Quick edit', $link->getText(), "'Quick edit' is the first contextual link for the block.");
+    $this->assertContains("/admin/structure/block/manage/$block_id/off-canvas?destination=user/2", $link->getAttribute('href'));
+
+    if (isset($toolbar_item)) {
+      // Check that you can open a toolbar tray and it will be closed after
+      // entering edit mode.
+      if ($element = $page->find('css', "#toolbar-administration a.is-active")) {
+        // If a tray was open from page load close it.
+        $element->click();
+        $this->waitForNoElement("#toolbar-administration a.is-active");
+      }
+      $page->find('css', $toolbar_item)->click();
+      $this->assertElementVisibleAfterWait('css', "{$toolbar_item}.is-active");
+    }
+    $this->enableEditMode();
+    if (isset($toolbar_item)) {
+      $this->waitForNoElement("{$toolbar_item}.is-active");
+    }
+    $this->openBlockForm($block_selector);
+    switch ($block_plugin) {
+      case 'system_powered_by_block':
+        // Confirm "Display Title" is not checked.
+        $web_assert->checkboxNotChecked('settings[label_display]');
+        // Confirm Title is not visible.
+        $this->assertEquals($this->isLabelInputVisible(), FALSE, 'Label is not visible');
+        $page->checkField('settings[label_display]');
+        $this->assertEquals($this->isLabelInputVisible(), TRUE, 'Label is visible');
+        // Fill out form, save the form.
+        $page->fillField('settings[label]', $new_page_text);
+
+        break;
+
+      case 'system_branding_block':
+        // Fill out form, save the form.
+        $page->fillField('settings[site_information][site_name]', $new_page_text);
+        break;
+
+      case 'settings_tray_test_class':
+        $web_assert->elementExists('css', '[data-drupal-selector="edit-settings-some-setting"]');
+        break;
+    }
+
+    if (isset($new_page_text)) {
+      $page->pressButton($button_text);
+      // Make sure the changes are present.
+      $new_page_text_locator = "$block_selector $label_selector:contains($new_page_text)";
+      $this->assertElementVisibleAfterWait('css', $new_page_text_locator);
+      // The page is loaded with the new change but make sure page is
+      // completely loaded.
+      $this->assertPageLoadComplete();
+    }
+
+    $this->openBlockForm($block_selector);
+
+    $this->disableEditMode();
+    // Canvas should close when editing module is closed.
+    $this->waitForOffCanvasToClose();
+
+    $this->enableEditMode();
+
+    // Open block form by clicking a element inside the block.
+    // This confirms that default action for links and form elements is
+    // suppressed.
+    $this->openBlockForm("$block_selector {$element_selector}", $block_selector);
+    $web_assert->elementTextContains('css', '.contextual-toolbar-tab button', 'Editing');
+    $web_assert->elementAttributeContains('css', '.dialog-off-canvas-main-canvas', 'class', 'js-settings-tray-edit-mode');
+    // Simulate press the Escape key.
+    $this->getSession()->executeScript('jQuery("body").trigger(jQuery.Event("keyup", { keyCode: 27 }));');
+    $this->waitForOffCanvasToClose();
+    $this->getSession()->wait(100);
+    $this->assertEditModeDisabled();
+    $web_assert->elementTextContains('css', '#drupal-live-announce', 'Exited edit mode.');
+    $web_assert->elementTextNotContains('css', '.contextual-toolbar-tab button', 'Editing');
+    $web_assert->elementAttributeNotContains('css', '.dialog-off-canvas-main-canvas', 'class', 'js-settings-tray-edit-mode');
+  }
+
+  /**
+   * Dataprovider for testBlocks().
+   */
+  public function providerTestBlocks() {
+    $blocks = [];
+    foreach ($this->getTestThemes() as $theme) {
+      $blocks += [
+        "$theme: block-powered" => [
+          'theme' => $theme,
+          'block_plugin' => 'system_powered_by_block',
+          'new_page_text' => 'Can you imagine anyone showing the label on this block',
+          'element_selector' => 'span a',
+          'label_selector' => 'h2',
+          'button_text' => 'Save Powered by Drupal',
+          'toolbar_item' => '#toolbar-item-user',
+        ],
+        "$theme: block-branding" => [
+          'theme' => $theme,
+          'block_plugin' => 'system_branding_block',
+          'new_page_text' => 'The site that will live a very short life',
+          'element_selector' => "a[rel='home']:last-child",
+          'label_selector' => "a[rel='home']:last-child",
+          'button_text' => 'Save Site branding',
+          'toolbar_item' => '#toolbar-item-administration',
+        ],
+        "$theme: block-search" => [
+          'theme' => $theme,
+          'block_plugin' => 'search_form_block',
+          'new_page_text' => NULL,
+          'element_selector' => '#edit-submit',
+          'label_selector' => 'h2',
+          'button_text' => 'Save Search form',
+          'toolbar_item' => NULL,
+        ],
+        // This is the functional JS test coverage accompanying
+        // \Drupal\Tests\settings_tray\Functional\SettingsTrayTest::testPossibleAnnotations().
+        "$theme: " . SettingsTrayFormAnnotationIsClassBlock::class => [
+          'theme' => $theme,
+          'block_plugin' => 'settings_tray_test_class',
+          'new_page_text' => NULL,
+          'element_selector' => 'span',
+          'label_selector' => NULL,
+          'button_text' => NULL,
+          'toolbar_item' => NULL,
+        ],
+        // This is the functional JS test coverage accompanying
+        // \Drupal\Tests\settings_tray\Functional\SettingsTrayTest::testPossibleAnnotations().
+        "$theme: " . SettingsTrayFormAnnotationNoneBlock::class => [
+          'theme' => $theme,
+          'block_plugin' => 'settings_tray_test_none',
+          'new_page_text' => NULL,
+          'element_selector' => 'span',
+          'label_selector' => NULL,
+          'button_text' => NULL,
+          'toolbar_item' => NULL,
+        ],
+      ];
+    }
+
+    return $blocks;
+  }
+
+  /**
+   * Enables edit mode by pressing edit button in the toolbar.
+   */
+  protected function enableEditMode() {
+    $this->pressToolbarEditButton();
+    $this->assertEditModeEnabled();
+  }
+
+  /**
+   * Disables edit mode by pressing edit button in the toolbar.
+   */
+  protected function disableEditMode() {
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->pressToolbarEditButton();
+    $this->assertEditModeDisabled();
+  }
+
+  /**
+   * Asserts that Off-Canvas block form is valid.
+   */
+  protected function assertOffCanvasBlockFormIsValid() {
+    $web_assert = $this->assertSession();
+    // Confirm that Block title display label has been changed.
+    $web_assert->elementTextContains('css', '.form-item-settings-label-display label', 'Display block title');
+    // Confirm Block title label is shown if checkbox is checked.
+    if ($this->getSession()->getPage()->find('css', 'input[name="settings[label_display]"]')->isChecked()) {
+      $this->assertEquals($this->isLabelInputVisible(), TRUE, 'Label is visible');
+      $web_assert->elementTextContains('css', '.form-item-settings-label label', 'Block title');
+    }
+    else {
+      $this->assertEquals($this->isLabelInputVisible(), FALSE, 'Label is not visible');
+    }
+
+    // Check that common block form elements exist.
+    $web_assert->elementExists('css', static::LABEL_INPUT_SELECTOR);
+    $web_assert->elementExists('css', 'input[data-drupal-selector="edit-settings-label-display"]');
+    // Check that advanced block form elements do not exist.
+    $web_assert->elementNotExists('css', 'input[data-drupal-selector="edit-visibility-request-path-pages"]');
+    $web_assert->elementNotExists('css', 'select[data-drupal-selector="edit-region"]');
+  }
+
+  /**
+   * Open block form by clicking the element found with a css selector.
+   *
+   * @param string $block_selector
+   *   A css selector selects the block or an element within it.
+   * @param string $contextual_link_container
+   *   The element that contains the contextual links. If none provide the
+   *   $block_selector will be used.
+   */
+  protected function openBlockForm($block_selector, $contextual_link_container = '') {
+    if (!$contextual_link_container) {
+      $contextual_link_container = $block_selector;
+    }
+    // Ensure that contextual link element is present because this is required
+    // to open the off-canvas dialog in edit mode.
+    $contextual_link = $this->assertSession()->waitForElement('css', "$contextual_link_container .contextual-links a");
+    $this->assertNotEmpty($contextual_link);
+    // When page first loads Edit Mode is not triggered until first contextual
+    // link is added.
+    $this->assertElementVisibleAfterWait('css', '.dialog-off-canvas-main-canvas.js-settings-tray-edit-mode');
+    // Ensure that all other Ajax activity is completed.
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->click($block_selector);
+    $this->waitForOffCanvasToOpen();
+    $this->assertOffCanvasBlockFormIsValid();
+  }
+
+  /**
+   * Tests QuickEdit links behavior.
+   */
+  public function testQuickEditLinks() {
+    $this->container->get('module_installer')->install(['quickedit']);
+    $this->grantPermissions(Role::load(RoleInterface::AUTHENTICATED_ID), ['access in-place editing']);
+    $quick_edit_selector = '#quickedit-entity-toolbar';
+    $node_selector = '[data-quickedit-entity-id="node/1"]';
+    $body_selector = '[data-quickedit-field-id="node/1/body/en/full"]';
+    $web_assert = $this->assertSession();
+    // Create a Content type and two test nodes.
+    $this->createContentType(['type' => 'page']);
+    $auth_role = Role::load(Role::AUTHENTICATED_ID);
+    $this->grantPermissions($auth_role, [
+      'edit any page content',
+      'access content',
+    ]);
+    $node = $this->createNode(
+      [
+        'title' => 'Page One',
+        'type' => 'page',
+        'body' => [
+          [
+            'value' => 'Regular NODE body for the test.',
+            'format' => 'plain_text',
+          ],
+        ],
+      ]
+    );
+    $page = $this->getSession()->getPage();
+    $block_plugin = 'system_powered_by_block';
+
+    foreach ($this->getTestThemes() as $theme) {
+
+      $this->enableTheme($theme);
+
+      $block = $this->placeBlock($block_plugin);
+      $block_selector = $this->getBlockSelector($block);
+      // Load the same page twice.
+      foreach ([1, 2] as $page_load_times) {
+        $this->drupalGet('node/' . $node->id());
+        // The 2nd page load we should already be in edit mode.
+        if ($page_load_times == 1) {
+          $this->enableEditMode();
+        }
+        // In Edit mode clicking field should open QuickEdit toolbar.
+        $page->find('css', $body_selector)->click();
+        $this->assertElementVisibleAfterWait('css', $quick_edit_selector);
+
+        $this->disableEditMode();
+        // Exiting Edit mode should close QuickEdit toolbar.
+        $web_assert->elementNotExists('css', $quick_edit_selector);
+        // When not in Edit mode QuickEdit toolbar should not open.
+        $page->find('css', $body_selector)->click();
+        $web_assert->elementNotExists('css', $quick_edit_selector);
+        $this->enableEditMode();
+        $this->openBlockForm($block_selector);
+        $page->find('css', $body_selector)->click();
+        $this->assertElementVisibleAfterWait('css', $quick_edit_selector);
+        // Off-canvas dialog should be closed when opening QuickEdit toolbar.
+        $this->waitForOffCanvasToClose();
+
+        $this->openBlockForm($block_selector);
+        // QuickEdit toolbar should be closed when opening Off-canvas dialog.
+        $web_assert->elementNotExists('css', $quick_edit_selector);
+      }
+      // Check using contextual links to invoke QuickEdit and open the tray.
+      $this->drupalGet('node/' . $node->id());
+      $web_assert->assertWaitOnAjaxRequest();
+      $this->disableEditMode();
+      // Open QuickEdit toolbar before going into Edit mode.
+      $this->clickContextualLink($node_selector, "Quick edit");
+      $this->assertElementVisibleAfterWait('css', $quick_edit_selector);
+      // Open off-canvas and enter Edit mode via contextual link.
+      $this->clickContextualLink($block_selector, "Quick edit");
+      $this->waitForOffCanvasToOpen();
+      // QuickEdit toolbar should be closed when opening off-canvas dialog.
+      $web_assert->elementNotExists('css', $quick_edit_selector);
+      // Open QuickEdit toolbar via contextual link while in Edit mode.
+      $this->clickContextualLink($node_selector, "Quick edit", FALSE);
+      $this->waitForOffCanvasToClose();
+      $this->assertElementVisibleAfterWait('css', $quick_edit_selector);
+      $this->disableEditMode();
+    }
+  }
+
+  /**
+   * Tests enabling and disabling Edit Mode.
+   */
+  public function testEditModeEnableDisable() {
+    foreach ($this->getTestThemes() as $theme) {
+      $this->enableTheme($theme);
+      $block = $this->placeBlock('system_powered_by_block');
+      foreach (['contextual_link', 'toolbar_link'] as $enable_option) {
+        $this->drupalGet('user');
+        $this->assertEditModeDisabled();
+        switch ($enable_option) {
+          // Enable Edit mode.
+          case 'contextual_link':
+            $this->clickContextualLink($this->getBlockSelector($block), "Quick edit");
+            $this->waitForOffCanvasToOpen();
+            $this->assertEditModeEnabled();
+            break;
+
+          case 'toolbar_link':
+            $this->enableEditMode();
+            break;
+        }
+        $this->disableEditMode();
+
+        // Make another page request to ensure Edit mode is still disabled.
+        $this->drupalGet('user');
+        $this->assertEditModeDisabled();
+        // Make sure on this page request it also re-enables and disables
+        // correctly.
+        $this->enableEditMode();
+        $this->disableEditMode();
+      }
+    }
+  }
+
+  /**
+   * Assert that edit mode has been properly enabled.
+   */
+  protected function assertEditModeEnabled() {
+    $web_assert = $this->assertSession();
+    // No contextual triggers should be hidden.
+    $web_assert->elementNotExists('css', '.contextual .trigger.visually-hidden');
+    // The toolbar edit button should read "Editing".
+    $web_assert->elementContains('css', static::TOOLBAR_EDIT_LINK_SELECTOR, 'Editing');
+    // The main canvas element should have the "js-settings-tray-edit-mode" class.
+    $web_assert->elementExists('css', '.dialog-off-canvas-main-canvas.js-settings-tray-edit-mode');
+  }
+
+  /**
+   * Assert that edit mode has been properly disabled.
+   */
+  protected function assertEditModeDisabled() {
+    $web_assert = $this->assertSession();
+    // Contextual triggers should be hidden.
+    $web_assert->elementExists('css', '.contextual .trigger.visually-hidden');
+    // No contextual triggers should be not hidden.
+    $web_assert->elementNotExists('css', '.contextual .trigger:not(.visually-hidden)');
+    // The toolbar edit button should read "Edit".
+    $web_assert->elementContains('css', static::TOOLBAR_EDIT_LINK_SELECTOR, 'Edit');
+    // The main canvas element should NOT have the "js-settings-tray-edit-mode"
+    // class.
+    $web_assert->elementNotExists('css', '.dialog-off-canvas-main-canvas.js-settings-tray-edit-mode');
+  }
+
+  /**
+   * Press the toolbar Edit button provided by the contextual module.
+   */
+  protected function pressToolbarEditButton() {
+    $this->assertSession()->waitForElement('css', '[data-contextual-id] .contextual-links a');
+    $edit_button = $this->getSession()
+      ->getPage()
+      ->find('css', static::TOOLBAR_EDIT_LINK_SELECTOR);
+    $edit_button->press();
+  }
+
+  /**
+   * Creates a custom block.
+   *
+   * @param bool|string $title
+   *   (optional) Title of block. When no value is given uses a random name.
+   *   Defaults to FALSE.
+   * @param string $bundle
+   *   (optional) Bundle name. Defaults to 'basic'.
+   * @param bool $save
+   *   (optional) Whether to save the block. Defaults to TRUE.
+   *
+   * @return \Drupal\block_content\Entity\BlockContent
+   *   Created custom block.
+   */
+  protected function createBlockContent($title = FALSE, $bundle = 'basic', $save = TRUE) {
+    $title = $title ?: $this->randomName();
+    $block_content = BlockContent::create([
+      'info' => $title,
+      'type' => $bundle,
+      'langcode' => 'en',
+      'body' => [
+        'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
+        'format' => 'plain_text',
+      ],
+    ]);
+    if ($block_content && $save === TRUE) {
+      $block_content->save();
+    }
+    return $block_content;
+  }
+
+  /**
+   * Creates a custom block type (bundle).
+   *
+   * @param string $label
+   *   The block type label.
+   * @param bool $create_body
+   *   Whether or not to create the body field.
+   *
+   * @return \Drupal\block_content\Entity\BlockContentType
+   *   Created custom block type.
+   */
+  protected function createBlockContentType($label, $create_body = FALSE) {
+    $bundle = BlockContentType::create([
+      'id' => $label,
+      'label' => $label,
+      'revision' => FALSE,
+    ]);
+    $bundle->save();
+    if ($create_body) {
+      block_content_add_body_field($bundle->id());
+    }
+    return $bundle;
+  }
+
+  /**
+   * Tests that contextual links in custom blocks are changed.
+   *
+   * "Quick edit" is quickedit.module link.
+   * "Quick edit settings" is settings_tray.module link.
+   */
+  public function testCustomBlockLinks() {
+    $this->container->get('module_installer')->install(['quickedit']);
+    $this->grantPermissions(Role::load(RoleInterface::AUTHENTICATED_ID), ['access in-place editing']);
+    $this->drupalGet('user');
+    $page = $this->getSession()->getPage();
+    $links = $page->findAll('css', "#block-custom .contextual-links li a");
+    $link_labels = [];
+    /** @var \Behat\Mink\Element\NodeElement $link */
+    foreach ($links as $link) {
+      $link_labels[$link->getAttribute('href')] = $link->getText();
+    }
+    $href = array_search('Quick edit', $link_labels);
+    $this->assertEquals('', $href);
+    $href = array_search('Quick edit settings', $link_labels);
+    $this->assertTrue(strstr($href, '/admin/structure/block/manage/custom/off-canvas?destination=user/2') !== FALSE);
+  }
+
+  /**
+   * Gets the block CSS selector.
+   *
+   * @param \Drupal\block\Entity\Block $block
+   *   The block.
+   *
+   * @return string
+   *   The CSS selector.
+   */
+  public function getBlockSelector(Block $block) {
+    return '#block-' . str_replace('_', '-', $block->id());
+  }
+
+  /**
+   * Determines if the label input is visible.
+   *
+   * @return bool
+   *   TRUE if the label is visible, FALSE if it is not.
+   */
+  protected function isLabelInputVisible() {
+    return $this->getSession()->getPage()->find('css', static::LABEL_INPUT_SELECTOR)->isVisible();
+  }
+
+  /**
+   * Test that validation errors appear in the off-canvas dialog.
+   */
+  public function testValidationMessages() {
+    $page = $this->getSession()->getPage();
+    $web_assert = $this->assertSession();
+    foreach ($this->getTestThemes() as $theme) {
+      $this->enableTheme($theme);
+      $block = $this->placeBlock('settings_tray_test_validation');
+      $this->drupalGet('user');
+      $this->enableEditMode();
+      $this->openBlockForm($this->getBlockSelector($block));
+      $page->pressButton('Save Block with validation error');
+      $web_assert->assertWaitOnAjaxRequest();
+      // The settings_tray_test_validation test plugin form always has a
+      // validation error.
+      $web_assert->elementContains('css', '#drupal-off-canvas', 'Sorry system error. Please save again');
+      $this->disableEditMode();
+      $block->delete();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getTestThemes() {
+    // Remove 'seven' theme. Setting Tray "Edit Mode" will not work with 'seven'
+    // because it removes all contextual links the off-canvas dialog should.
+    return array_filter(parent::getTestThemes(), function ($theme) {
+      return $theme !== 'seven';
+    });
+  }
+
+  /**
+   * Tests that blocks with configuration overrides are disabled.
+   */
+  public function testOverriddenBlock() {
+    $web_assert = $this->assertSession();
+    $page = $this->getSession()->getPage();
+    $overridden_block = $this->placeBlock('system_powered_by_block', [
+      'id' => 'overridden_block',
+      'label_display' => 1,
+      'label' => 'This will be overridden.',
+    ]);
+    $this->drupalGet('user');
+    $block_selector = $this->getBlockSelector($overridden_block);
+    // Confirm the block is marked as Settings Tray editable.
+    $this->assertEquals('editable', $page->find('css', $block_selector)->getAttribute('data-drupal-settingstray'));
+    // Confirm the label is not overridden.
+    $web_assert->elementContains('css', $block_selector, 'This will be overridden.');
+    $this->enableEditMode();
+    $this->openBlockForm($block_selector);
+
+    // Confirm the block Settings Tray functionality is disabled when block is
+    // overridden.
+    $this->container->get('state')->set('settings_tray_override_test.block', TRUE);
+    $overridden_block->save();
+    $block_config = \Drupal::configFactory()->getEditable('block.block.overridden_block');
+    $block_config->set('settings', $block_config->get('settings'))->save();
+
+    $this->drupalGet('user');
+    $this->assertOverriddenBlockDisabled($overridden_block, 'Now this will be the label.');
+
+    // Test a non-overridden block does show the form in the off-canvas dialog.
+    $block = $this->placeBlock('system_powered_by_block', [
+      'label_display' => 1,
+      'label' => 'Labely label',
+    ]);
+    $this->drupalGet('user');
+    $block_selector = $this->getBlockSelector($block);
+    // Confirm the block is marked as Settings Tray editable.
+    $this->assertEquals('editable', $page->find('css', $block_selector)->getAttribute('data-drupal-settingstray'));
+    // Confirm the label is not overridden.
+    $web_assert->elementContains('css', $block_selector, 'Labely label');
+    $this->openBlockForm($block_selector);
+  }
+
+  /**
+   * Test  blocks with overridden related configuration removed when overridden.
+   */
+  public function testOverriddenConfigurationRemoved() {
+    $web_assert = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    // Confirm the branding block does include 'site_information' section when
+    // the site name is not overridden.
+    $branding_block = $this->placeBlock('system_branding_block');
+    $this->drupalGet('user');
+    $this->enableEditMode();
+    $this->openBlockForm($this->getBlockSelector($branding_block));
+    $web_assert->fieldExists('settings[site_information][site_name]');
+    // Confirm the branding block does not include 'site_information' section
+    // when the site name is overridden.
+    $this->container->get('state')->set('settings_tray_override_test.site_name', TRUE);
+    $this->drupalGet('user');
+    $this->openBlockForm($this->getBlockSelector($branding_block));
+    $web_assert->fieldNotExists('settings[site_information][site_name]');
+    $page->pressButton('Save Site branding');
+    $this->assertElementVisibleAfterWait('css', 'div:contains(The block configuration has been saved)');
+    $web_assert->assertWaitOnAjaxRequest();
+    // Confirm we did not save changes to the configuration.
+    $this->assertEquals('Llama Fan Club', \Drupal::configFactory()->get('system.site')->get('name'));
+    $this->assertEquals('Drupal', \Drupal::configFactory()->getEditable('system.site')->get('name'));
+
+    // Add a link or the menu will not render.
+    $menu_link_content = MenuLinkContent::create([
+      'title' => 'This is on the menu',
+      'menu_name' => 'main',
+      'link' => ['uri' => 'route:<front>'],
+    ]);
+    $menu_link_content->save();
+    // Confirm the menu block does include menu section when the menu is not
+    // overridden.
+    $menu_block = $this->placeBlock('system_menu_block:main');
+    $web_assert->assertWaitOnAjaxRequest();
+    $this->drupalGet('user');
+    $web_assert->pageTextContains('This is on the menu');
+    $this->openBlockForm($this->getBlockSelector($menu_block));
+    $web_assert->elementExists('css', '#menu-overview');
+
+    // Confirm the menu block does not include menu section when the menu is
+    // overridden.
+    $this->container->get('state')->set('settings_tray_override_test.menu', TRUE);
+    $this->drupalGet('user');
+    $web_assert->pageTextContains('This is on the menu');
+    $menu_with_overrides = \Drupal::configFactory()->get('system.menu.main')->get();
+    $menu_without_overrides = \Drupal::configFactory()->getEditable('system.menu.main')->get();
+    $this->openBlockForm($this->getBlockSelector($menu_block));
+    $web_assert->elementNotExists('css', '#menu-overview');
+    $page->pressButton('Save Main navigation');
+    $this->assertElementVisibleAfterWait('css', 'div:contains(The block configuration has been saved)');
+    $web_assert->assertWaitOnAjaxRequest();
+    // Confirm we did not save changes to the configuration.
+    $this->assertEquals('Labely label', \Drupal::configFactory()->get('system.menu.main')->get('label'));
+    $this->assertEquals('Main navigation', \Drupal::configFactory()->getEditable('system.menu.main')->get('label'));
+    $this->assertEquals($menu_with_overrides, \Drupal::configFactory()->get('system.menu.main')->get());
+    $this->assertEquals($menu_without_overrides, \Drupal::configFactory()->getEditable('system.menu.main')->get());
+    $web_assert->pageTextContains('This is on the menu');
+  }
+  /**
+   * Asserts that an overridden block has Settings Tray disabled.
+   *
+   * @param \Drupal\block\Entity\Block $overridden_block
+   *   The overridden block.
+   * @param string $override_text
+   *   The override text that should appear in the block.
+   */
+  protected function assertOverriddenBlockDisabled(Block $overridden_block, $override_text) {
+    $web_assert = $this->assertSession();
+    $page = $this->getSession()->getPage();
+    $block_selector = $this->getBlockSelector($overridden_block);
+    $block_id = $overridden_block->id();
+    // Confirm the block does not have a quick edit link.
+    $contextual_links = $page->findAll('css', "$block_selector .contextual-links li a");
+    $this->assertNotEmpty($contextual_links);
+    foreach ($contextual_links as $link) {
+      $this->assertNotContains("/admin/structure/block/manage/$block_id/off-canvas", $link->getAttribute('href'));
+    }
+    // Confirm the block is not marked as Settings Tray editable.
+    $this->assertFalse($page->find('css', $block_selector)
+      ->hasAttribute('data-drupal-settingstray'));
+
+    // Confirm the text is actually overridden.
+    $web_assert->elementContains('css', $this->getBlockSelector($overridden_block), $override_text);
+  }
+
+}
