diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 329ff58..9667198 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1127,66 +1127,6 @@ function template_preprocess_maintenance_task_list(&$variables) {
 }
 
 /**
- * Adds a default set of helper variables for preprocessors and templates.
- *
- * This function is called for theme hooks implemented as templates only, not
- * for theme hooks implemented as functions. This preprocess function is the
- * first in the sequence of preprocessing functions that are called when
- * preparing variables for a template.
- *
- * See the @link themeable Default theme implementations topic @endlink for
- * details.
- */
-function template_preprocess(&$variables, $hook, $info) {
-  // Merge in variables that don't depend on hook and don't change during a
-  // single page request.
-  // Use the advanced drupal_static() pattern, since this is called very often.
-  static $drupal_static_fast;
-  if (!isset($drupal_static_fast)) {
-    $drupal_static_fast['default_variables'] = &drupal_static(__FUNCTION__);
-  }
-  $default_variables = &$drupal_static_fast['default_variables'];
-  if (!isset($default_variables)) {
-    $default_variables = _template_preprocess_default_variables();
-  }
-  $variables += $default_variables;
-
-  // When theming a render element, merge its #attributes into
-  // $variables['attributes'].
-  if (isset($info['render element'])) {
-    $key = $info['render element'];
-    if (isset($variables[$key]['#attributes'])) {
-      $variables['attributes'] = NestedArray::mergeDeep($variables['attributes'], $variables[$key]['#attributes']);
-    }
-  }
-}
-
-/**
- * Returns hook-independent variables to template_preprocess().
- */
-function _template_preprocess_default_variables() {
-  // Variables that don't depend on a database connection.
-  $variables = array(
-    'attributes' => array(),
-    'title_attributes' => array(),
-    'content_attributes' => array(),
-    'title_prefix' => array(),
-    'title_suffix' => array(),
-    'db_is_active' => !defined('MAINTENANCE_MODE'),
-    'is_admin' => FALSE,
-    'logged_in' => FALSE,
-  );
-
-  // Give modules a chance to alter the default template variables.
-  \Drupal::moduleHandler()->alter('template_preprocess_default_variables', $variables);
-
-  // Tell all templates where they are located.
-  $variables['directory'] = \Drupal::theme()->getActiveTheme()->getPath();
-
-  return $variables;
-}
-
-/**
  * Prepares variables for HTML document templates.
  *
  * Default template: html.html.twig.
diff --git a/core/lib/Drupal/Core/Render/theme.api.php b/core/lib/Drupal/Core/Render/theme.api.php
index e9144cf..2c840d0 100644
--- a/core/lib/Drupal/Core/Render/theme.api.php
+++ b/core/lib/Drupal/Core/Render/theme.api.php
@@ -105,8 +105,10 @@
  * theme engine name). Modules, themes, and theme engines can provide these
  * functions to modify how the data is preprocessed, before it is passed to the
  * theme template:
- * - template_preprocess(&$variables, $hook): Creates a default set of variables
- *   for all theme hooks with template implementations. Provided by Drupal Core.
+ * - ThemeManager service's addDefaultTemplateVariables(&$variables): Creates
+ *   a default set of variables for all theme hooks with template
+ *   implementations. Provided by Drupal Core. You can alter these variables by
+ *   using HOOK_template_preprocess_default_variables_alter(&$variables).
  * - template_preprocess_HOOK(&$variables): Should be implemented by the module
  *   that registers the theme hook, to set up default variables.
  * - MODULE_preprocess(&$variables, $hook): hook_preprocess() is invoked on all
@@ -1045,7 +1047,7 @@ function hook_page_bottom(array &$page_bottom) {
  *   - render element: Used for render element items only: the name of the
  *     renderable element or element tree to pass to the theme function. This
  *     name is used as the name of the variable that holds the renderable
- *     element or tree in preprocess and process functions.
+ *     element or tree in preprocess functions.
  *   - file: The file the implementation resides in. This file will be included
  *     prior to the theme being rendered, to make sure that the function or
  *     preprocess function (as needed) is actually loaded; this makes it
@@ -1154,10 +1156,9 @@ function hook_theme($existing, $type, $theme, $path) {
  *     'content' => NULL,
  *   ),
  *   'preprocess functions' => array (
- *     0 => 'template_preprocess',
- *     1 => 'template_preprocess_block_content_add_list',
- *     2 => 'contextual_preprocess',
- *     3 => 'seven_preprocess_block_content_add_list',
+ *     0 => 'template_preprocess_block_content_add_list',
+ *     1 => 'contextual_preprocess',
+ *     2 => 'seven_preprocess_block_content_add_list',
  *   ),
  * );
  * @endcode
@@ -1178,17 +1179,19 @@ function hook_theme_registry_alter(&$theme_registry) {
 }
 
 /**
- * Alter the default, hook-independent variables for all templates.
+ * Alter the default variables for all templates.
  *
  * Allows modules to provide additional default template variables or manipulate
- * existing. This hook is invoked from template_preprocess() after basic default
- * template variables have been set up and before the next template preprocess
- * function is invoked.
+ * existing. This hook is invoked from ThemeManager service's
+ * getDefaultTemplateVariables() method after basic default template variables
+ * have been set up and before the template preprocess functions are invoked.
  *
  * Note that the default template variables are statically cached within a
  * request. When adding a template variable that depends on other context, it is
  * your responsibility to appropriately reset the static cache in
- * template_preprocess() when needed:
+ * ThemeManager's getDefaultTemplateVariables() when needed:
+ * @todo Change 'template_preprocess' key for Drupal 8.1 or 9 because
+ *   template_preprocess() is removed.
  * @code
  * drupal_static_reset('template_preprocess');
  * @endcode
@@ -1197,10 +1200,10 @@ function hook_theme_registry_alter(&$theme_registry) {
  *
  * @param array $variables
  *   An associative array of default template variables, as set up by
- *   _template_preprocess_default_variables(). Passed by reference.
- *
- * @see template_preprocess()
- * @see _template_preprocess_default_variables()
+ *   Drupal/Core/Theme/ThemeManagerInterface::getDefaultTemplateVariables().
+ *   Passed by reference.
+ * @see Drupal/Core/Theme/ThemeManagerInterface::addDefaultTemplateVariables()
+ * @see Drupal/Core/Theme/ThemeManagerInterface::getDefaultTemplateVariables()
  */
 function hook_template_preprocess_default_variables_alter(&$variables) {
   $variables['is_admin'] = \Drupal::currentUser()->hasPermission('access administration pages');
diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php
index 6fb3733..d48cc48 100644
--- a/core/lib/Drupal/Core/Theme/Registry.php
+++ b/core/lib/Drupal/Core/Theme/Registry.php
@@ -505,6 +505,7 @@ protected function processExtension(array &$cache, $name, $type, $theme, $path)
         if (!isset($info['preprocess functions']) || !is_array($info['preprocess functions'])) {
           $info['preprocess functions'] = array();
           $prefixes = array();
+
           if ($type == 'module') {
             // Default variable preprocessor prefix.
             $prefixes[] = 'template';
@@ -602,7 +603,7 @@ protected function completeSuggestion($hook, array &$cache) {
       // @see https://www.drupal.org/node/2457295
       if (isset($cache[$previous_hook]) && !isset($cache[$previous_hook]['incomplete preprocess functions'])) {
         $cache[$hook] = $incomplete_previous_hook + $cache[$previous_hook];
-        if (isset($incomplete_previous_hook['preprocess functions'])) {
+        if (!empty($incomplete_previous_hook['preprocess functions'])) {
           $diff = array_diff($incomplete_previous_hook['preprocess functions'], $cache[$previous_hook]['preprocess functions']);
           $cache[$hook]['preprocess functions'] = array_merge($cache[$previous_hook]['preprocess functions'], $diff);
         }
diff --git a/core/lib/Drupal/Core/Theme/ThemeManager.php b/core/lib/Drupal/Core/Theme/ThemeManager.php
index 9958b56..ea764a7 100644
--- a/core/lib/Drupal/Core/Theme/ThemeManager.php
+++ b/core/lib/Drupal/Core/Theme/ThemeManager.php
@@ -14,6 +14,7 @@
 use Symfony\Component\HttpFoundation\RequestStack;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Template\Attribute;
+use Drupal\Component\Utility\NestedArray;
 
 /**
  * Provides the default implementation of a theme manager.
@@ -286,6 +287,16 @@ public function render($hook, array $variables) {
         $theme_hook_suggestion = $hook;
       }
     }
+
+    // Set default variables before preprocess hooks.
+    $this->addDefaultTemplateVariables($variables);
+    $render_element_type = isset($info['render element']) ? $info['render element'] : NULL;
+    $this->addElementAttributeVariables($variables, $render_element_type);
+
+    // Invoke preprocess hooks.
+    // By default $info['preprocess functions'] should always be set, but it's
+    // good to check it if default Registry service implementation is
+    // overridden. See \Drupal\Core\Theme\Registry.
     if (isset($info['preprocess functions'])) {
       foreach ($info['preprocess functions'] as $preprocessor_function) {
         if (function_exists($preprocessor_function)) {
@@ -340,22 +351,6 @@ public function render($hook, array $variables) {
           }
         }
       }
-
-      // In some cases, a template implementation may not have had
-      // template_preprocess() run (for example, if the default implementation
-      // is a function, but a template overrides that default implementation).
-      // In these cases, a template should still be able to expect to have
-      // access to the variables provided by template_preprocess(), so we add
-      // them here if they don't already exist. We don't want the overhead of
-      // running template_preprocess() twice, so we use the 'directory' variable
-      // to determine if it has already run, which while not completely
-      // intuitive, is reasonably safe, and allows us to save on the overhead of
-      // adding some new variable to track that.
-      if (!isset($variables['directory'])) {
-        $default_template_variables = array();
-        template_preprocess($default_template_variables, $hook, $info);
-        $variables += $default_template_variables;
-      }
       if (!isset($default_attributes)) {
         $default_attributes = new Attribute();
       }
@@ -467,4 +462,56 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
     $this->alterForTheme($theme, $type, $data, $context1, $context2);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function addDefaultTemplateVariables(&$variables) {
+    $variables += $this->getDefaultTemplateVariables();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultTemplateVariables() {
+    static $drupal_static_fast;
+    if (!isset($drupal_static_fast)) {
+      // @todo Change 'template_preprocess' key for Drupal 8.1 or 9 because
+      //   template_preprocess() is removed.
+      // @see hook_template_preprocess_default_variables_alter()
+      $drupal_static_fast['default_variables'] = &drupal_static('template_preprocess');
+    }
+    $default_variables = &$drupal_static_fast['default_variables'];
+    if(!isset($default_variables)) {
+      // Variables that don't depend on a database connection.
+      $default_variables = array(
+        'attributes' => array(),
+        'title_attributes' => array(),
+        'content_attributes' => array(),
+        'title_prefix' => array(),
+        'title_suffix' => array(),
+        'db_is_active' => !defined('MAINTENANCE_MODE'),
+        'is_admin' => FALSE,
+        'logged_in' => FALSE,
+      );
+
+      // Give modules a chance to alter default template variables.
+      \Drupal::moduleHandler()->alter('template_preprocess_default_variables', $default_variables);
+      // Tell all templates where they are located.
+      $default_variables['directory'] = \Drupal::theme()->getActiveTheme()->getPath();
+    }
+    return $default_variables;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addElementAttributeVariables(&$variables, $render_element_type) {
+    if (isset($variables[$render_element_type]['#attributes'])) {
+      $variables['attributes'] = NestedArray::mergeDeep(
+        $variables['attributes'],
+        $variables[$render_element_type]['#attributes']
+      );
+    }
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php b/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php
index b61ff84..a8bc0dd 100644
--- a/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php
+++ b/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php
@@ -145,4 +145,29 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL);
    */
   public function alterForTheme(ActiveTheme $theme, $type, &$data, &$context1 = NULL, &$context2 = NULL);
 
+  /**
+   * Adds a default set of helper variables for preprocessors and templates.
+   *
+   * This function is run before template preprocessing functions that are
+   * called when preparing variables for a template. Template preprocessing
+   * functions have a form of template_preprocess_HOOK($variables).
+   *
+   * See the @link themeable Default theme implementations topic @endlink for
+   * details.
+   */
+  public function addDefaultTemplateVariables(&$variables);
+
+  /**
+   * TODO Write better docs.
+   * Returns template variables that are set for every template by default.
+   */
+  public function getDefaultTemplateVariables();
+
+  /**
+   * TODO Write better docs.
+   * When theming an element, this function merges its #attributes into
+   * $variables['attributes']. This will happen only if the $render_element_type
+   * with key #attributes is found from the $variables.
+   */
+  public function addElementAttributeVariables(&$variables, $render_element_type);
 }
diff --git a/core/modules/config_translation/templates/config_translation_manage_form_element.html.twig b/core/modules/config_translation/templates/config_translation_manage_form_element.html.twig
index 672f567..8976609 100644
--- a/core/modules/config_translation/templates/config_translation_manage_form_element.html.twig
+++ b/core/modules/config_translation/templates/config_translation_manage_form_element.html.twig
@@ -8,7 +8,7 @@
  *   - source: The source of the translation.
  *   - translation: The translation for the target language.
  *
- * @see template_preprocess()
+ * @see Drupal/Core/Theme/ThemeManagerInterface::addDefaultTemplateVariables()
  * @see template_preprocess_config_translation_manage_form_element()
  *
  * @ingroup themeable
diff --git a/core/modules/link/templates/link-formatter-link-separate.html.twig b/core/modules/link/templates/link-formatter-link-separate.html.twig
index 469cd9a..e8eda33 100644
--- a/core/modules/link/templates/link-formatter-link-separate.html.twig
+++ b/core/modules/link/templates/link-formatter-link-separate.html.twig
@@ -8,7 +8,7 @@
  * - title: (optional) A descriptive or alternate title for the link, which may
  *   be different than the actual link text.
  *
- * @see template_preprocess()
+ * @see Drupal/Core/Theme/ThemeManagerInterface::addDefaultTemplateVariables()
  * @see template_preprocess_link_formatter_link_separate()
  *
  * @ingroup themeable
diff --git a/core/modules/responsive_image/templates/responsive-image.html.twig b/core/modules/responsive_image/templates/responsive-image.html.twig
index a3a51a6..10ce241 100644
--- a/core/modules/responsive_image/templates/responsive-image.html.twig
+++ b/core/modules/responsive_image/templates/responsive-image.html.twig
@@ -7,7 +7,7 @@
  * - sources: The attributes of the <source> tags for this <picture> tag.
  * - img_element: The controlling image, with the fallback image in srcset.
  *
- * @see template_preprocess()
+ * @see Drupal/Core/Theme/ThemeManagerInterface::addDefaultTemplateVariables()
  * @see template_preprocess_responsive_image()
  *
  * @ingroup themeable
diff --git a/core/modules/system/src/Tests/Theme/RegistryTest.php b/core/modules/system/src/Tests/Theme/RegistryTest.php
index 1fc3715..1eadd6a 100644
--- a/core/modules/system/src/Tests/Theme/RegistryTest.php
+++ b/core/modules/system/src/Tests/Theme/RegistryTest.php
@@ -81,7 +81,6 @@ public function testMultipleSubThemes() {
 
     $preprocess_functions = $registry_subsub_theme->get()['theme_test_template_test']['preprocess functions'];
     $this->assertIdentical([
-      'template_preprocess',
       'test_basetheme_preprocess_theme_test_template_test',
       'test_subtheme_preprocess_theme_test_template_test',
       'test_subsubtheme_preprocess_theme_test_template_test',
@@ -89,14 +88,12 @@ public function testMultipleSubThemes() {
 
     $preprocess_functions = $registry_sub_theme->get()['theme_test_template_test']['preprocess functions'];
     $this->assertIdentical([
-      'template_preprocess',
       'test_basetheme_preprocess_theme_test_template_test',
       'test_subtheme_preprocess_theme_test_template_test',
     ], $preprocess_functions);
 
     $preprocess_functions = $registry_base_theme->get()['theme_test_template_test']['preprocess functions'];
     $this->assertIdentical([
-      'template_preprocess',
       'test_basetheme_preprocess_theme_test_template_test',
     ], $preprocess_functions);
 
@@ -114,7 +111,6 @@ public function testSuggestionPreprocessFunctions() {
 
     $suggestions = ['__kitten', '__flamingo'];
     $expected_preprocess_functions = [
-      'template_preprocess',
       'theme_test_preprocess_theme_test_preprocess_suggestions',
     ];
     $suggestion = '';
@@ -127,7 +123,6 @@ public function testSuggestionPreprocessFunctions() {
     } while ($suggestion = array_shift($suggestions));
 
     $expected_preprocess_functions = [
-      'template_preprocess',
       'theme_test_preprocess_theme_test_preprocess_suggestions',
       'test_theme_preprocess_theme_test_preprocess_suggestions',
       'test_theme_preprocess_theme_test_preprocess_suggestions__kitten',
diff --git a/core/modules/system/templates/dropbutton-wrapper.html.twig b/core/modules/system/templates/dropbutton-wrapper.html.twig
index ca0ff7e..e1ea086 100644
--- a/core/modules/system/templates/dropbutton-wrapper.html.twig
+++ b/core/modules/system/templates/dropbutton-wrapper.html.twig
@@ -6,7 +6,7 @@
  * Available variables:
  * - children: Contains the child elements of the dropbutton menu.
  *
- * @see template_preprocess()
+ * @see Drupal/Core/Theme/ThemeManagerInterface::addDefaultTemplateVariables()
  * @see template_preprocess_dropbutton_wrapper()
  *
  * @ingroup themeable
diff --git a/core/themes/classy/templates/field/link-formatter-link-separate.html.twig b/core/themes/classy/templates/field/link-formatter-link-separate.html.twig
index cb94264..35c58e5 100644
--- a/core/themes/classy/templates/field/link-formatter-link-separate.html.twig
+++ b/core/themes/classy/templates/field/link-formatter-link-separate.html.twig
@@ -8,7 +8,7 @@
  * - title: (optional) A descriptive or alternate title for the link, which may
  *   be different than the actual link text.
  *
- * @see template_preprocess()
+ * @see Drupal/Core/Theme/ThemeManagerInterface::addDefaultTemplateVariables()
  * @see template_preprocess_link_formatter_link_separate()
  */
 #}
diff --git a/core/themes/classy/templates/form/dropbutton-wrapper.html.twig b/core/themes/classy/templates/form/dropbutton-wrapper.html.twig
index 5dccc85..9694195 100644
--- a/core/themes/classy/templates/form/dropbutton-wrapper.html.twig
+++ b/core/themes/classy/templates/form/dropbutton-wrapper.html.twig
@@ -6,7 +6,7 @@
  * Available variables:
  * - children: Contains the child elements of the dropbutton menu.
  *
- * @see template_preprocess()
+ * @see Drupal/Core/Theme/ThemeManagerInterface::addDefaultTemplateVariables()
  * @see template_preprocess_dropbutton_wrapper()
  */
 #}
