 core/core.services.yml                             |  4 +--
 core/lib/Drupal/Core/Access/RouteProcessorCsrf.php | 18 +++++++---
 core/lib/Drupal/Core/Form/FormBuilder.php          | 38 +++++++++++++---------
 .../Drupal/Core/Render/PlaceholderGenerator.php    | 19 +++++++++++
 .../Core/Render/PlaceholderGeneratorInterface.php  |  2 ++
 5 files changed, 58 insertions(+), 23 deletions(-)

diff --git a/core/core.services.yml b/core/core.services.yml
index 9bf8562..3818b51 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -333,7 +333,7 @@ services:
     arguments: ['@stream_wrapper_manager', '@settings', '@logger.channel.file']
   form_builder:
     class: Drupal\Core\Form\FormBuilder
-    arguments: ['@form_validator', '@form_submitter', '@form_cache', '@module_handler', '@event_dispatcher', '@request_stack', '@class_resolver', '@element_info', '@theme.manager', '@?csrf_token']
+    arguments: ['@form_validator', '@form_submitter', '@form_cache', '@module_handler', '@event_dispatcher', '@request_stack', '@class_resolver', '@element_info', '@theme.manager', '@render_placeholder_generator', '@?csrf_token']
   form_validator:
     class: Drupal\Core\Form\FormValidator
     arguments: ['@request_stack', '@string_translation', '@csrf_token', '@logger.channel.form', '@form_error_handler']
@@ -1176,7 +1176,7 @@ services:
     class: Drupal\Core\Access\RouteProcessorCsrf
     tags:
       - { name: route_processor_outbound }
-    arguments: ['@csrf_token']
+    arguments: ['@csrf_token', '@render_placeholder_generator']
   transliteration:
     class: Drupal\Core\Transliteration\PhpTransliteration
     arguments: [null, '@module_handler']
diff --git a/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php b/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
index bd3a552..d16ac9a 100644
--- a/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
+++ b/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Access;
 
 use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\Render\PlaceholderGeneratorInterface;
 use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
 use Symfony\Component\Routing\Route;
 
@@ -24,13 +25,23 @@ class RouteProcessorCsrf implements OutboundRouteProcessorInterface {
   protected $csrfToken;
 
   /**
+   * The placeholder generator.
+   *
+   * @var \Drupal\Core\Render\PlaceholderGeneratorInterface
+   */
+  protected $placeholderGenerator;
+
+  /**
    * Constructs a RouteProcessorCsrf object.
    *
    * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
    *   The CSRF token generator.
+   * @param \Drupal\Core\Render\PlaceholderGeneratorInterface $placeholder_generator
+   *   The placeholder generator.
    */
-  function __construct(CsrfTokenGenerator $csrf_token) {
+  function __construct(CsrfTokenGenerator $csrf_token, PlaceholderGeneratorInterface $placeholder_generator) {
     $this->csrfToken = $csrf_token;
+    $this->placeholderGenerator = $placeholder_generator;
   }
 
   /**
@@ -50,10 +61,7 @@ public function processOutbound($route_name, Route $route, array &$parameters, B
       }
       else {
         // Generate a placeholder and a render array to replace it.
-        $placeholder = hash('sha1', $path);
-        $placeholder_render_array = [
-          '#lazy_builder' => ['route_processor_csrf:renderPlaceholderCsrfToken', [$path]],
-        ];
+        list($placeholder, $placeholder_render_array) = $this->placeholderGenerator->createPlainPlaceholder('route_processor_csrf:renderPlaceholderCsrfToken', [$path]);
 
         // Instead of setting an actual CSRF token as the query string, we set
         // the placeholder, which will be replaced at the very last moment. This
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index bb7034f..d503f4a 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -19,6 +19,7 @@
 use Drupal\Core\Form\Exception\BrokenPostRequestException;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Render\ElementInfoManagerInterface;
+use Drupal\Core\Render\PlaceholderGeneratorInterface;
 use Drupal\Core\Theme\ThemeManagerInterface;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\HttpFoundation\FileBag;
@@ -106,6 +107,13 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
   protected $formCache;
 
   /**
+   * The placeholder generator.
+   *
+   * @var \Drupal\Core\Render\PlaceholderGeneratorInterface
+   */
+  protected $placeholderGenerator;
+
+  /**
    * Defines element value callables which are safe to run even when the form
    * state has an invalid CSRF token.
    *
@@ -165,10 +173,12 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
    *   The element info manager.
    * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
    *   The theme manager.
+   * @param \Drupal\Core\Render\PlaceholderGeneratorInterface $placeholder_generator
+   *   The placeholder generator.
    * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
    *   The CSRF token generator.
    */
-  public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, FormCacheInterface $form_cache, ModuleHandlerInterface $module_handler, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ElementInfoManagerInterface $element_info, ThemeManagerInterface $theme_manager, CsrfTokenGenerator $csrf_token = NULL) {
+  public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, FormCacheInterface $form_cache, ModuleHandlerInterface $module_handler, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ElementInfoManagerInterface $element_info, ThemeManagerInterface $theme_manager, PlaceholderGeneratorInterface $placeholder_generator, CsrfTokenGenerator $csrf_token = NULL) {
     $this->formValidator = $form_validator;
     $this->formSubmitter = $form_submitter;
     $this->formCache = $form_cache;
@@ -178,6 +188,7 @@ public function __construct(FormValidatorInterface $form_validator, FormSubmitte
     $this->classResolver = $class_resolver;
     $this->elementInfo = $element_info;
     $this->csrfToken = $csrf_token;
+    $this->placeholderGenerator = $placeholder_generator;
     $this->themeManager = $theme_manager;
   }
 
@@ -654,16 +665,15 @@ public function renderPlaceholderFormAction() {
   /**
    * #lazy_builder callback; renders form CSRF token.
    *
-   * @param string $placeholder
-   *  A string containing a placeholder, matching the value of the form's
-   *  #token.
+   * @param string $form_id
+   *  A string containing a form ID, matching the value of the form's #token.
    *
    * @return array
    *   A renderable array containing the CSRF token.
    */
-  public function renderFormTokenPlaceholder($placeholder) {
+  public function renderFormTokenPlaceholder($form_id) {
     return [
-      '#markup' => $this->csrfToken->get($placeholder),
+      '#markup' => $this->csrfToken->get($form_id),
       '#cache' => [
         'contexts' => [
           'session',
@@ -687,11 +697,8 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
       // dynamically generated action URLs don't have poor cacheability.
       // Use the proper API to generate the placeholder, when we have one. See
       // https://www.drupal.org/node/2562341.
-      $placeholder = 'form_action_' . hash('crc32b', __METHOD__);
-
-      $form['#attached']['placeholders'][$placeholder] = [
-        '#lazy_builder' => ['form_builder:renderPlaceholderFormAction', []],
-      ];
+      list($placeholder, $placeholder_render_array) = $this->placeholderGenerator->createPlainPlaceholder('form_builder:renderPlaceholderFormAction', [], 'form_action');
+      $form['#attached']['placeholders'][$placeholder] = $placeholder_render_array;
       $form['#action'] = $placeholder;
     }
 
@@ -746,9 +753,10 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
       $form['#cache']['contexts'][] = 'user.roles:authenticated';
       if ($user && $user->isAuthenticated()) {
         // Generate a public token based on the form id.
+        $form['#token'] = $form_id;
+
         // Generates a placeholder based on the form ID.
-        $placeholder = 'form_token_placeholder_' . hash('crc32b', $form_id);
-        $form['#token'] = $placeholder;
+        list($placeholder, $placeholder_render_array) = $this->placeholderGenerator->createPlainPlaceholder('form_builder:renderFormTokenPlaceholder', [$form_id], 'form_token_placeholder');
 
         $form['form_token'] = array(
           '#id' => Html::getUniqueId('edit-' . $form_id . '-form-token'),
@@ -764,9 +772,7 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
           // token don't have poor cacheability.
           '#attached' => [
             'placeholders' => [
-              $placeholder => [
-                '#lazy_builder' => ['form_builder:renderFormTokenPlaceholder', [$placeholder]]
-              ]
+              $placeholder => $placeholder_render_array,
             ]
           ],
           '#cache' => [
diff --git a/core/lib/Drupal/Core/Render/PlaceholderGenerator.php b/core/lib/Drupal/Core/Render/PlaceholderGenerator.php
index a6c4a23..2c7375b 100644
--- a/core/lib/Drupal/Core/Render/PlaceholderGenerator.php
+++ b/core/lib/Drupal/Core/Render/PlaceholderGenerator.php
@@ -99,4 +99,23 @@ public function createPlaceholder(array $element) {
     return $placeholder_element;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function createPlainPlaceholder($callback, array $args, $prefix = '') {
+    $placeholder = hash('crc32b', serialize([$callback, $args]));
+    if ($prefix) {
+      $placeholder = $prefix . '_' . $placeholder;
+    }
+
+    $placeholder_render_array = [
+      '#lazy_builder' => [$callback, $args],
+    ];
+
+    return [
+      $placeholder,
+      $placeholder_render_array,
+    ];
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Render/PlaceholderGeneratorInterface.php b/core/lib/Drupal/Core/Render/PlaceholderGeneratorInterface.php
index 89802a2..eddb584 100644
--- a/core/lib/Drupal/Core/Render/PlaceholderGeneratorInterface.php
+++ b/core/lib/Drupal/Core/Render/PlaceholderGeneratorInterface.php
@@ -62,4 +62,6 @@ public function shouldAutomaticallyPlaceholder(array $element);
    */
   public function createPlaceholder(array $element);
 
+  public function createPlainPlaceholder($callback, array $args, $prefix = '');
+
 }
