diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index f98aadd..85b51f9 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -436,37 +436,20 @@ function drupal_find_base_themes($themes, $key) {
  * engine, it overrides the default implementation of the 'page' theme hook by
  * containing a 'page.html.twig' file within its folder structure).
  *
- * @subsection sub_preprocess_templates Preprocessing for Template Files
- * If the implementation is a template file, several functions are called before
- * the template file is invoked to modify the $variables array. These make up
- * the "preprocessing" phase, and are executed (if they exist), in the following
- * order (note that in the following list, HOOK indicates the theme hook name,
- * MODULE indicates a module name, THEME indicates a theme name, and ENGINE
- * indicates a theme engine name):
- * - template_preprocess(&$variables, $hook): Creates a default set of variables
- *   for all theme hooks with template implementations.
- * - 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
- *   implementing modules.
- * - MODULE_preprocess_HOOK(&$variables): hook_preprocess_HOOK() is invoked on
- *   all implementing modules, so that modules that didn't define the theme hook
- *   can alter the variables.
- * - ENGINE_engine_preprocess(&$variables, $hook): Allows the theme engine to
- *   set necessary variables for all theme hooks with template implementations.
- * - ENGINE_engine_preprocess_HOOK(&$variables): Allows the theme engine to set
- *   necessary variables for the particular theme hook.
- * - THEME_preprocess(&$variables, $hook): Allows the theme to set necessary
- *   variables for all theme hooks with template implementations.
- * - THEME_preprocess_HOOK(&$variables): Allows the theme to set necessary
- *   variables specific to the particular theme hook.
- *
- * @subsection sub_preprocess_theme_funcs Preprocessing for Theme Functions
- * If the implementation is a function, only the theme-hook-specific preprocess
- * functions (the ones ending in _HOOK) are called from the list above. This is
- * because theme hooks with function implementations need to be fast, and
- * calling the non-theme-hook-specific preprocess functions for them would incur
- * a noticeable performance penalty.
+ * @subsection sub_prepare_variables Preparing Variables for Theme Hooks
+ * Several functions are invoked before theme functions or template files are
+ * provided their available $variables. These functions make up the "prepare"
+ * phase, and are executed (if they exist), in the following
+ * order (HOOK indicates the profile, module, theme engine or theme name,
+ * THEME_HOOK indicates the name of the specific theme hook called):
+ * - HOOK_theme_prepare(&$variables, $hook): Prepare a set of variables
+ *   for all theme hook implementations.
+ * - HOOK_theme_prepare_THEME_HOOK(&$variables): Prepare a set of
+ *   variables for a specific theme hook implementation.
+ * - HOOK_theme_prepare_alter(&$variables, $hook): Alter a set of prepared
+ *   variables for all theme hook implementations.
+ * - HOOK_theme_prepare_THEME_HOOK_alter(&$variables, $context): Alter a set of
+ *   prepared variables for a specific theme hook implementation.
  *
  * @subsection sub_alternate_suggestions Suggesting Alternate Hooks
  * Alternate hooks can be suggested by implementing the hook-specific
@@ -482,11 +465,11 @@ function drupal_find_base_themes($themes, $key) {
  *
  * @param $hook
  *   The name of the theme hook to call. If the name contains a
- *   double-underscore ('__') and there isn't an implementation for the full
- *   name, the part before the '__' is checked. This allows a fallback to a
+ *   double-underscore ('__') and there is not an implementation for the full
+ *   name, the name before the '__' is checked. This allows a fallback to a
  *   more generic implementation. For example, if theme('links__node', ...) is
  *   called, but there is no implementation of that theme hook, then the
- *   'links' implementation is used. This process is iterative, so if
+ *   'links' implementation is used instead. This process is iterative, so if
  *   theme('links__contextual__node', ...) is called, theme() checks for the
  *   following implementations, and uses the first one that exists:
  *   - links__contextual__node
@@ -500,7 +483,7 @@ function drupal_find_base_themes($themes, $key) {
  *   convention is not desired or is insufficient.
  * @param $variables
  *   An associative array of variables to merge with defaults from the theme
- *   registry, pass to preprocess functions for modification, and finally, pass
+ *   registry, pass to prepare functions for modification, and finally, pass
  *   to the function or template implementing the theme hook. Alternatively,
  *   this can be a renderable array, in which case, its properties are mapped to
  *   variables expected by the theme hook implementations.
@@ -513,6 +496,8 @@ function drupal_find_base_themes($themes, $key) {
  * @see themeable
  * @see hook_theme()
  * @see template_preprocess()
+ *
+ * @throws Exception
  */
 function theme($hook, $variables = array()) {
   static $default_attributes;
@@ -604,10 +589,6 @@ function theme($hook, $variables = array()) {
   elseif (!empty($info['render element'])) {
     $variables += array($info['render element'] => array());
   }
-  // Supply original caller info.
-  $variables += array(
-    'theme_hook_original' => $original_hook,
-  );
 
   // Set base hook for later use. For example if '#theme' => 'node__article'
   // is called, we run hook_theme_suggestions_node_alter() rather than
@@ -620,6 +601,12 @@ function theme($hook, $variables = array()) {
     $base_theme_hook = $hook;
   }
 
+  // Supply original caller info.
+  $variables += array(
+    'theme_hook' => $base_theme_hook,
+    'theme_hook_original' => $original_hook,
+  );
+
   // Invoke hook_theme_suggestions_HOOK().
   $suggestions = Drupal::moduleHandler()->invokeAll('theme_suggestions_' . $base_theme_hook, array($variables));
   // If theme() was invoked with a direct theme suggestion like
@@ -668,6 +655,37 @@ function theme($hook, $variables = array()) {
       $info['preprocess functions'] = $base_hook_info['preprocess functions'];
     }
   }
+
+  // A list of hooks that will be invoked for the given theme hook.
+  $prepare_hooks = array(
+    'theme_prepare',
+    'theme_prepare_' . $base_theme_hook,
+  );
+  // If the original theme hook is not the same as the base theme hook, then
+  // invoke the original theme hook suggestion after the base theme hook.
+  if ($hook !== $original_hook) {
+    $prepare_hooks[] = 'theme_prepare_' . $original_hook;
+  }
+
+  // Send prepare arguments to invoke hooks with $variables passed by reference.
+  // @todo Change this to hook_menu() style where you need to return $variables
+  //   instead of altering by reference.
+  $prepare_args = array(&$variables, $base_theme_hook);
+
+  // Invokes hook_theme_prepare(), hook_theme_prepare_THEME_HOOK() for enabled
+  // modules.
+  foreach ($prepare_hooks as $prepare_hook) {
+    foreach (\Drupal::moduleHandler()->getImplementations($prepare_hook) as $module) {
+      $prepare_function = $module . '_' . $prepare_hook;
+      if (function_exists($prepare_function)) {
+        call_user_func_array($prepare_function, $prepare_args);
+      }
+    }
+  }
+
+  // Invokes hook_preprocess() and hook_preprocess_HOOK() implementations. This
+  // phase is deprecated and is run in between hook_theme_prepare[_THEME_HOOK]()
+  // and hook_theme_prepare[_THEME_HOOK]_alter().
   if (isset($info['preprocess functions'])) {
     foreach ($info['preprocess functions'] as $preprocessor_function) {
       if (function_exists($preprocessor_function)) {
@@ -676,6 +694,12 @@ function theme($hook, $variables = array()) {
     }
   }
 
+  // Invokes hook_theme_prepare_alter(), hook_theme_prepare_THEME_ID_alter()
+  // for enabled modules.
+  foreach ($prepare_hooks as $prepare_hook) {
+    Drupal::moduleHandler()->alter($prepare_hook, $variables, $base_theme_hook);
+  }
+
   // Generate the output using either a function or a template.
   $output = '';
   if (isset($info['function'])) {
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemePrepareTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemePrepareTest.php
new file mode 100644
index 0000000..fa6c486
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemePrepareTest.php
@@ -0,0 +1,142 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Theme\ThemePrepareTest.
+ */
+
+namespace Drupal\system\Tests\Theme;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests hook_theme_prepare() and hook_theme_prepare_alter() implementations.
+ */
+class ThemePrepareTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('theme_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Theme Prepare API',
+      'description' => 'Test theme prepare and alter functions.',
+      'group' => 'Theme',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+    theme_enable(array('test_theme', 'test_basetheme', 'test_subtheme'));
+  }
+
+  /**
+   * Ensures prepare hooks work and are invoked and in the correct order.
+   */
+  public function testThemePrepare() {
+    \Drupal::config('system.theme')
+      ->set('default', 'test_theme')
+      ->save();
+    $this->drupalGet('theme-test/prepare');
+
+    $this->assertRaw('<p>moduleVariable1: Drupal\theme_test\ThemeTestController::prepare<br>
+theme_test_theme_prepare_alter<br>
+test_theme_theme_prepare_alter<br>
+test_theme_theme_prepare_theme_test_prepare_alter</p>');
+
+    $this->assertRaw('<p>moduleVariable2: Drupal\theme_test\ThemeTestController::prepare<br>
+theme_test_theme_prepare_alter<br>
+test_theme_theme_prepare_alter<br>
+test_theme_theme_prepare_theme_test_prepare_alter</p>');
+
+    $this->assertRaw('<p>moduleVariableSuggestion1: Drupal\theme_test\ThemeTestController::prepare<br>
+theme_test_theme_prepare_alter<br>
+test_theme_theme_prepare_alter<br>
+test_theme_theme_prepare_theme_test_prepare_alter</p>');
+
+    $this->assertRaw('<p>moduleVariableSuggestion2: Drupal\theme_test\ThemeTestController::prepare<br>
+theme_test_theme_prepare_theme_test_prepare__implemented<br>
+theme_test_theme_prepare_alter<br>
+test_theme_theme_prepare_alter<br>
+test_theme_theme_prepare_theme_test_prepare_alter<br>
+theme_test_theme_prepare_theme_test_prepare__implemented_alter<br>
+test_theme_theme_prepare_theme_test_prepare__implemented_alter</p>');
+
+    $this->assertRaw('<p>moduleVariableSuggestion3: Drupal\theme_test\ThemeTestController::prepare<br>
+theme_test_theme_prepare_theme_test_prepare__implemented__extended<br>
+theme_test_theme_prepare_alter<br>
+test_theme_theme_prepare_alter<br>
+test_theme_theme_prepare_theme_test_prepare_alter<br>
+theme_test_theme_prepare_theme_test_prepare__implemented__extended_alter<br>
+test_theme_theme_prepare_theme_test_prepare__implemented__extended_alter</p>');
+
+    $this->assertRaw('<p>moduleElement1: Drupal\theme_test\ThemeTestController::prepare<br>
+theme_test_theme_prepare_alter<br>
+test_theme_theme_prepare_alter<br>
+test_theme_theme_prepare_theme_test_prepare_alter</p>');
+
+    $this->assertRaw('<p>moduleInjectedVariable1: Variable added in: theme_test_theme_prepare<br>
+theme_test_theme_prepare_alter<br>
+test_theme_theme_prepare_alter<br>
+test_theme_theme_prepare_theme_test_prepare_alter</p>');
+
+    $this->assertRaw('<p>moduleInjectedVariable2: Variable added in: theme_test_theme_prepare_theme_test_prepare_element<br>
+theme_test_theme_prepare_alter<br>
+test_theme_theme_prepare_alter<br>
+test_theme_theme_prepare_theme_test_prepare_alter</p>');
+
+    $this->assertRaw('<p>moduleInjectedVariable3: Variable added in: theme_test_theme_prepare_theme_test_prepare_element_alter<br>
+theme_test_theme_prepare_alter<br>
+test_theme_theme_prepare_alter<br>
+test_theme_theme_prepare_theme_test_prepare_alter</p>');
+
+    $this->assertRaw('<p>themeInjectedVariable1: Variable added in: test_theme_theme_prepare_theme_test_prepare_element_alter<br>
+theme_test_theme_prepare_alter<br>
+test_theme_theme_prepare_alter<br>
+test_theme_theme_prepare_theme_test_prepare_alter</p>');
+
+    $this->assertRaw('<p>moduleElementSuggestion1: Drupal\theme_test\ThemeTestController::prepare<br>
+theme_test_theme_prepare_alter<br>
+test_theme_theme_prepare_alter<br>
+test_theme_theme_prepare_theme_test_prepare_alter</p>');
+
+    $this->assertRaw('<p>moduleElementSuggestion2: Drupal\theme_test\ThemeTestController::prepare<br>
+theme_test_theme_prepare_alter<br>
+test_theme_theme_prepare_alter<br>
+test_theme_theme_prepare_theme_test_prepare_alter</p>');
+
+    $this->assertRaw('<p>moduleElementSuggestion3: Drupal\theme_test\ThemeTestController::prepare<br>
+theme_test_theme_prepare_alter<br>
+test_theme_theme_prepare_alter<br>
+test_theme_theme_prepare_theme_test_prepare_alter</p>');
+  }
+
+  /**
+   * Ensures prepare hooks for subthemes work.
+   */
+  function testThemePrepareSubtheme() {
+    \Drupal::config('system.theme')
+      ->set('default', 'test_subtheme')
+      ->save();
+    $this->drupalGet('theme-test/prepare-subtheme');
+
+    $this->assertRaw('<p>subThemePrepare: Drupal\theme_test\ThemeTestController::prepareSubtheme<br>
+theme_test_theme_prepare_alter<br>
+test_basetheme_theme_prepare_theme_test_prepare_alter<br>
+test_subtheme_theme_prepare_theme_test_prepare_alter</p>');
+
+    $this->assertRaw('<p>subThemePrepareSuggestion: Drupal\theme_test\ThemeTestController::prepareSubtheme<br>
+theme_test_theme_prepare_theme_test_prepare__implemented<br>
+theme_test_theme_prepare_alter<br>
+test_basetheme_theme_prepare_theme_test_prepare_alter<br>
+test_subtheme_theme_prepare_theme_test_prepare_alter<br>
+theme_test_theme_prepare_theme_test_prepare__implemented_alter<br>
+test_basetheme_theme_prepare_theme_test_prepare__implemented_alter<br>
+test_subtheme_theme_prepare_theme_test_prepare__implemented_alter</p>');
+  }
+
+}
diff --git a/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php b/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php
index 29bfa4e..2efdab1 100644
--- a/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php
+++ b/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php
@@ -127,4 +127,87 @@ function functionSuggestionAlter() {
     return array('#theme' => 'theme_test_function_suggestions');
   }
 
+  /**
+   * Menu callback for testing hook_theme[_THEME_HOOK]_prepare[_alter]().
+   */
+  function prepare() {
+    return array(
+      'moduleVariable1' => array(
+        '#theme' => 'theme_test_prepare',
+        '#title' => 'moduleVariable1',
+        '#value' => __METHOD__,
+      ),
+      'moduleVariable2' => array(
+        '#theme' => 'theme_test_prepare',
+        '#title' => 'moduleVariable2',
+        '#value' => __METHOD__,
+      ),
+      'moduleVariableSuggestion1' => array(
+        '#theme' => 'theme_test_prepare__fallback',
+        '#title' => 'moduleVariableSuggestion1',
+        '#value' => __METHOD__,
+      ),
+      'moduleVariableSuggestion2' => array(
+        '#theme' => 'theme_test_prepare__implemented',
+        '#title' => 'moduleVariableSuggestion2',
+        '#value' => __METHOD__,
+      ),
+      'moduleVariableSuggestion3' => array(
+        '#theme' => 'theme_test_prepare__implemented__extended',
+        '#title' => 'moduleVariableSuggestion3',
+        '#value' => __METHOD__,
+      ),
+      'moduleElement1' => array(
+        '#theme' => 'theme_test_prepare_element',
+        'moduleElement1' => array(
+          '#theme' => 'theme_test_prepare',
+          '#title' => 'moduleElement1',
+          '#value' => __METHOD__,
+        ),
+      ),
+      'moduleElementSuggestion1' => array(
+        '#theme' => 'theme_test_prepare_element__fallback',
+        'moduleElementSuggestion1' => array(
+          '#theme' => 'theme_test_prepare',
+          '#title' => 'moduleElementSuggestion1',
+          '#value' => __METHOD__,
+        ),
+      ),
+      'moduleElementSuggestion2' => array(
+        '#theme' => 'theme_test_prepare_element__implemented',
+        'moduleElementSuggestion2' => array(
+          '#theme' => 'theme_test_prepare',
+          '#title' => 'moduleElementSuggestion2',
+          '#value' => __METHOD__,
+        ),
+      ),
+      'moduleElementSuggestion3' => array(
+        '#theme' => 'theme_test_prepare_element__implemented__extended',
+        'moduleElementSuggestion3' => array(
+          '#theme' => 'theme_test_prepare',
+          '#title' => 'moduleElementSuggestion3',
+          '#value' => __METHOD__,
+        ),
+      ),
+    );
+  }
+
+  /**
+   * Menu callback for testing prepare hooks with subthemes.
+   */
+  function prepareSubtheme() {
+    return array(
+      'subThemePrepare' => array(
+        '#theme' => 'theme_test_prepare',
+        '#title' => 'subThemePrepare',
+        '#value' => __METHOD__,
+      ),
+      'subThemePrepareSuggestion' => array(
+        '#theme' => 'theme_test_prepare__implemented',
+        '#title' => 'subThemePrepareSuggestion',
+        '#value' => __METHOD__,
+      ),
+    );
+  }
+
 }
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module
index bdb1404..8c300ed 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.module
+++ b/core/modules/system/tests/modules/theme_test/theme_test.module
@@ -49,6 +49,13 @@ function theme_test_theme($existing, $type, $theme, $path) {
   $info['test_theme_not_existing_function'] = array(
     'function' => 'test_theme_not_existing_function',
   );
+  // hook_theme_prepare() theme hooks.
+  $items['theme_test_prepare'] = array(
+    'variables' => array('hook' => NULL, 'title' => NULL, 'value' => NULL),
+  );
+  $items['theme_test_prepare_element'] = array(
+    'render element' => 'element',
+  );
   return $items;
 }
 
@@ -81,6 +88,10 @@ function theme_test_menu() {
     'theme callback' => '_theme_custom_theme',
     'route_name' => 'theme_test.function_template_override',
   );
+  $items['theme-test/prepare'] = array(
+    'theme callback' => '_theme_custom_theme',
+    'route_name' => 'theme_test.prepare',
+  );
   return $items;
 }
 
@@ -151,6 +162,22 @@ function theme_theme_test_function_suggestions($variables) {
 }
 
 /**
+ * Returns HTML for theme_test_prepare theme hook.
+ */
+function theme_theme_test_prepare($variables) {
+  $title = $variables['title'];
+  $value = $variables['value'];
+  return "<p>$title: $value</p>";
+}
+
+/**
+ * Theme function for testing rendering of theme_test_prepare_element().
+ */
+function theme_theme_test_prepare_element($variables) {
+  return drupal_render($variables['element']);
+}
+
+/**
  * Implements hook_theme_suggestions_HOOK().
  */
 function theme_test_theme_suggestions_theme_test_suggestion_provided(array $variables) {
@@ -170,3 +197,76 @@ function theme_test_theme_suggestions_alter(array &$suggestions, array $variable
 function theme_test_theme_suggestions_theme_test_suggestions_alter(array &$suggestions, array $variables) {
   drupal_set_message(__FUNCTION__ . '() executed.');
 }
+
+/**
+ * Implements hook_theme_prepare().
+ */
+function theme_test_theme_prepare(&$variables, $hook) {
+  // Only prepare variables for a specific base theme hook.
+  if ($hook === 'theme_test_prepare_element') {
+    $variables['element']['moduleInjectedVariable1'] = array(
+      '#theme' => 'theme_test_prepare__element',
+      '#title' => 'moduleInjectedVariable1',
+      '#value' => 'Variable added in: ' . __FUNCTION__,
+    );
+  }
+}
+
+/**
+ * Implements hook_theme_prepare_alter().
+ */
+function theme_test_theme_prepare_alter(&$variables, $hook) {
+  if ($hook === 'theme_test_prepare') {
+    $variables['value'] .= "<br>\n" . __FUNCTION__;
+  }
+}
+
+/**
+ * Implements hook_theme_prepare_THEME_HOOK__SUGGESTION().
+ */
+function theme_test_theme_prepare_theme_test_prepare__implemented(&$variables) {
+  $variables['value'] .= "<br>\n" . __FUNCTION__;
+}
+
+/**
+ * Implements hook_theme_prepare_THEME_HOOK__SUGGESTION_alter().
+ */
+function theme_test_theme_prepare_theme_test_prepare__implemented_alter(&$variables) {
+  $variables['value'] .= "<br>\n" . __FUNCTION__;
+}
+
+/**
+ * Implements hook_theme_prepare_THEME_HOOK__SUGGESTION().
+ */
+function theme_test_theme_prepare_theme_test_prepare__implemented__extended(&$variables) {
+  $variables['value'] .= "<br>\n" . __FUNCTION__;
+}
+
+/**
+ * Implements hook_theme_prepare_THEME_HOOK__SUGGESTION_alter().
+ */
+function theme_test_theme_prepare_theme_test_prepare__implemented__extended_alter(&$variables) {
+  $variables['value'] .= "<br>\n" . __FUNCTION__;
+}
+
+/**
+ * Implements hook_theme_prepare_THEME_HOOK().
+ */
+function theme_test_theme_prepare_theme_test_prepare_element(&$variables) {
+  $variables['element']['moduleInjectedVariable2'] = array(
+    '#theme' => 'theme_test_prepare__element',
+    '#title' => 'moduleInjectedVariable2',
+    '#value' => 'Variable added in: ' . __FUNCTION__,
+  );
+}
+
+/**
+ * Implements hook_theme_prepare_THEME_HOOK_alter().
+ */
+function theme_test_theme_prepare_theme_test_prepare_element_alter(&$variables) {
+  $variables['element']['moduleInjectedVariable3'] = array(
+    '#theme' => 'theme_test_prepare__element',
+    '#title' => 'moduleInjectedVariable3',
+    '#value' => 'Variable added in: ' . __FUNCTION__,
+  );
+}
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.routing.yml b/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
index 19f2b8c..e0b7d7c 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
+++ b/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
@@ -92,3 +92,17 @@ function_suggestion_alter:
     _content: '\Drupal\theme_test\ThemeTestController::functionSuggestionAlter'
   requirements:
     _access: 'TRUE'
+
+theme_test.prepare:
+  path: '/theme-test/prepare'
+  defaults:
+    _content: '\Drupal\theme_test\ThemeTestController::prepare'
+  requirements:
+    _access: 'TRUE'
+
+theme_test.prepare_alter:
+  path: '/theme-test/prepare-subtheme'
+  defaults:
+    _content: '\Drupal\theme_test\ThemeTestController::prepareSubtheme'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/system/tests/themes/test_basetheme/test_basetheme.theme b/core/modules/system/tests/themes/test_basetheme/test_basetheme.theme
new file mode 100644
index 0000000..473e289
--- /dev/null
+++ b/core/modules/system/tests/themes/test_basetheme/test_basetheme.theme
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Theme to help test base themes.
+ */
+
+/**
+ * Implements hook_theme_prepare_THEME_HOOK_alter().
+ */
+function test_basetheme_theme_prepare_theme_test_prepare_alter(&$variables) {
+  $variables['value'] .= "<br>\n" . __FUNCTION__;
+}
+
+/**
+ * Implements hook_theme_prepare_THEME_HOOK__SUGGESTION_alter().
+ */
+function test_basetheme_theme_prepare_theme_test_prepare__implemented_alter(&$variables) {
+  $variables['value'] .= "<br>\n" . __FUNCTION__;
+}
diff --git a/core/modules/system/tests/themes/test_subtheme/test_subtheme.theme b/core/modules/system/tests/themes/test_subtheme/test_subtheme.theme
new file mode 100644
index 0000000..6ba3eb6
--- /dev/null
+++ b/core/modules/system/tests/themes/test_subtheme/test_subtheme.theme
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Theme to help test subthemes.
+ */
+
+/**
+ * Implements hook_theme_prepare_THEME_HOOK_alter().
+ */
+function test_subtheme_theme_prepare_theme_test_prepare_alter(&$variables) {
+  $variables['value'] .= "<br>\n" . __FUNCTION__;
+}
+
+/**
+ * Implements hook_theme_prepare_THEME_HOOK__SUGGESTION_alter().
+ */
+function test_subtheme_theme_prepare_theme_test_prepare__implemented_alter(&$variables) {
+  $variables['value'] .= "<br>\n" . __FUNCTION__;
+}
diff --git a/core/modules/system/tests/themes/test_theme/test_theme.theme b/core/modules/system/tests/themes/test_theme/test_theme.theme
index 13cb2b9..01f47b9 100644
--- a/core/modules/system/tests/themes/test_theme/test_theme.theme
+++ b/core/modules/system/tests/themes/test_theme/test_theme.theme
@@ -84,3 +84,44 @@ function test_theme_theme_test_function_suggestions__theme_override($variables)
 function test_theme_theme_test_function_suggestions__module_override($variables) {
   return 'Theme function overridden based on new theme suggestion provided by a module.';
 }
+
+/**
+ * Implements hook_theme_prepare_alter().
+ */
+function test_theme_theme_prepare_alter(&$variables, $hook) {
+  if ($hook === 'theme_test_prepare') {
+    $variables['value'] .= "<br>\n" . __FUNCTION__;
+  }
+}
+
+/**
+ * Implements hook_theme_prepare_THEME_HOOK_alter().
+ */
+function test_theme_theme_prepare_theme_test_prepare_element_alter(&$variables) {
+  $variables['element']['themeInjectedVariable1'] = array(
+    '#theme' => 'theme_test_prepare__element',
+    '#title' => 'themeInjectedVariable1',
+    '#value' => 'Variable added in: ' . __FUNCTION__,
+  );
+}
+
+/**
+ * Implements hook_theme_prepare_THEME_HOOK_alter().
+ */
+function test_theme_theme_prepare_theme_test_prepare_alter(&$variables) {
+  $variables['value'] .= "<br>\n" . __FUNCTION__;
+}
+
+/**
+ * Implements hook_theme_prepare_THEME_HOOK__SUGGESTION_alter().
+ */
+function test_theme_theme_prepare_theme_test_prepare__implemented_alter(&$variables) {
+  $variables['value'] .= "<br>\n" . __FUNCTION__;
+}
+
+/**
+ * Implements hook_theme_prepare_THEME_HOOK__SUGGESTION_alter().
+ */
+function test_theme_theme_prepare_theme_test_prepare__implemented__extended_alter(&$variables) {
+  $variables['value'] .= "<br>\n" . __FUNCTION__;
+}
diff --git a/core/modules/system/theme.api.php b/core/modules/system/theme.api.php
index db11a21..10c7d61 100644
--- a/core/modules/system/theme.api.php
+++ b/core/modules/system/theme.api.php
@@ -104,9 +104,15 @@ function hook_form_system_theme_settings_alter(&$form, &$form_state) {
  *   The variables array (modify in place).
  * @param $hook
  *   The name of the theme hook.
+ *
+ * @deprecated
+ * @see hook_theme_prepare()
  */
 function hook_preprocess(&$variables, $hook) {
- static $hooks;
+  // This hook has been deprecated, do not use it. Use the following instead:
+  // hook_theme_prepare().
+
+  static $hooks;
 
   // Add contextual links to the variables, if the user has permission.
 
@@ -150,14 +156,149 @@ function hook_preprocess(&$variables, $hook) {
  *
  * @param $variables
  *   The variables array (modify in place).
+ *
+ * @deprecated
+ * @see hook_theme_prepare_THEME_HOOK()
  */
 function hook_preprocess_HOOK(&$variables) {
+  // This hook has been deprecated, do not use it. Use the following instead:
+  // hook_theme_prepare_THEME_HOOK().
+
+  // This example is from rdf_preprocess_image(). It adds an RDF attribute
+  // to the image hook's variables.
+  $variables['attributes']['typeof'] = array('foaf:Image');
+}
+
+/**
+ * Prepares variables for theme functions and templates.
+ *
+ * This hook allows any module to prepare variables for any theme hook.
+ * hook_theme_prepare_THEME_HOOK() can be used to prepare variables for a
+ * specific theme hook.
+ *
+ * For more detailed information, see theme().
+ *
+ * @param array $variables
+ *   The associative array of variables, passed by reference (modify in place).
+ * @param string $hook
+ *   The name of the theme hook called.
+ *
+ * @see hook_theme_prepare_alter()
+ * @see hook_theme_prepare_THEME_HOOK()
+ * @see hook_theme_prepare_THEME_HOOK_alter()
+ */
+function hook_theme_prepare(&$variables, $hook) {
+  static $hooks;
+
+  // Add contextual links to the variables, if the user has permission.
+  if (!user_access('access contextual links')) {
+    return;
+  }
+
+  if (!isset($hooks)) {
+    $hooks = theme_get_registry();
+  }
+
+  // Determine the primary theme function argument.
+  if (isset($hooks[$hook]['variables'])) {
+    $keys = array_keys($hooks[$hook]['variables']);
+    $key = $keys[0];
+  }
+  else {
+    $key = $hooks[$hook]['render element'];
+  }
+
+  if (isset($key) && !empty($variables[$key])) {
+    $element = $variables[$key];
+  }
+
+  if (isset($element) && is_array($element) && !empty($element['#contextual_links'])) {
+    $variables['title_suffix']['contextual_links'] = contextual_links_view($element);
+    if (!empty($variables['title_suffix']['contextual_links'])) {
+      $variables['attributes']['class'][] = 'contextual-links-region';
+    }
+  }
+}
+
+/**
+ * Alters the prepared variables used in theme functions and templates.
+ *
+ * This hook allows any module or theme to alter existing variables provided by
+ * earlier invocations of hook_theme_prepare() or
+ * hook_theme_prepare_THEME_HOOK(). This hook should only be used when it is
+ * needed to override or add to the $variables array for a theme hook it did not
+ * define.
+ *
+ * For more detailed information, see theme().
+ *
+ * @param array $variables
+ *   The associative array of variables, passed by reference (modify in place).
+ * @param string $hook
+ *   The name of the theme hook called.
+ *
+ * @see hook_theme_prepare()
+ * @see hook_theme_prepare_THEME_HOOK()
+ * @see hook_theme_prepare_THEME_HOOK_alter()
+ */
+function hook_theme_prepare_alter(&$variables, $hook) {
+  // Replace the name of the user whom authored article nodes.
+  if ($hook === 'node__article') {
+    $variables['name'] = t('Anonymous Unicorn');
+  }
+}
+
+/**
+ * Prepares variables for a specific theme hook or theme hook suggestion.
+ *
+ * This hook allows any module to prepare theme variables for a specific theme
+ * hook or theme hook suggestion.
+ *
+ * THEME_HOOK can be either the base theme hook or a more specific suggestion
+ * using the double-underscore ('__') notation.
+ *
+ * For more detailed information, see theme().
+ *
+ * @param array $variables
+ *   The associative array of variables, passed by reference (modify in place).
+ *
+ * @see hook_theme_prepare_THEME_HOOK_alter()
+ * @see hook_theme_prepare_alter()
+ * @see hook_theme_prepare()
+ */
+function hook_theme_prepare_THEME_HOOK(&$variables) {
   // This example is from rdf_preprocess_image(). It adds an RDF attribute
   // to the image hook's variables.
   $variables['attributes']['typeof'] = array('foaf:Image');
 }
 
 /**
+ * Alters prepared variables for a specific theme hook or theme hook suggestion.
+ *
+ * This hook allows any module or theme to alter existing variables provided by
+ * earlier invocations of hook_theme_prepare() or
+ * hook_theme_prepare_THEME_HOOK(). It should only be used when it is needed
+ * to override or add to the theme variable for a theme hook it did not define.
+ *
+ * THEME_HOOK can be either the base theme hook or a more specific suggestion
+ * using the double-underscore ('__') notation.
+ *
+ * For more detailed information, see theme().
+ *
+ * @param array $variables
+ *   The associative array of variables, passed by reference (modify in place).
+ *
+ * @see hook_theme_prepare_THEME_HOOK()
+ * @see hook_theme_prepare_alter()
+ * @see hook_theme_prepare()
+ */
+function hook_theme_prepare_THEME_HOOK_alter(&$variables) {
+  // Replace the name of the user whom authored article nodes; assuming
+  // THEME_HOOK was "node__article" and placed in node.module, the function name
+  // would read: node_theme_prepare_node__article_alter.
+  $variables['name'] = t('Anonymous Unicorn');
+}
+
+/**
  * Provides alternate named suggestions for a specific theme hook.
  *
  * This hook allows the module implementing hook_theme() for a theme hook to
@@ -173,8 +314,8 @@ function hook_preprocess_HOOK(&$variables) {
  * @todo Add @code sample.
  *
  * @param array $variables
- *   An array of variables passed to the theme hook. Note that this hook is
- *   invoked before any preprocessing.
+ *   An array of variables passed to the theme hook. NOTE: this hook is invoked
+ *   prior to hook_theme_prepare(). Some variables may not always be available.
  *
  * @return array
  *   An array of theme suggestions.
