diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index e49bb76..5f4bcdb 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1055,6 +1055,48 @@ function theme($hook, $variables = array()) {
       $info['preprocess functions'] = $base_hook_info['preprocess functions'];
     }
   }
+
+  // Provide context for hooks.
+  $context = array(
+    '#theme_id' => $original_hook,
+    '#base_theme_id' => $hook,
+    '#suggestions' => $suggestions,
+  );
+
+  $invoke_hooks = array(
+    'theme_prepare',
+    'theme_prepare_' . $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) {
+    $invoke_hooks[] = 'theme_prepare_' . $original_hook;
+  }
+
+  // Invoke theme_prepare hooks for enabled modules.
+  foreach ($invoke_hooks as $invoke_hook) {
+    $variables = NestedArray::mergeDeep($variables, (array) Drupal::moduleHandler()->invokeAll($invoke_hook, array($context)));
+  }
+
+  // Invoke hooks for the active theme. The active theme takes precedence over
+  // modules.
+  // @todo Replace with Drupal::themeHandler()->invoke() once implemented.
+  // @see https://drupal.org/node/2029819
+  foreach ($invoke_hooks as $invoke_hook) {
+    $variables = NestedArray::mergeDeep($variables, (array) Drupal::moduleHandler()->themeInvoke($invoke_hook, array($context)));
+  }
+
+  // Clone the context so it cannot be altered by modules or themes.
+  $context_clone = $context;
+
+  // Invoke alter hooks for enabled modules.
+  // @todo Add Drupal::themeHandler()->alter() invocations once implemented.
+  // @see https://drupal.org/node/2029819
+  foreach ($invoke_hooks as $invoke_hook) {
+    Drupal::moduleHandler()->alter($invoke_hook, $variables, $context_clone);
+  }
+
+  // @todo Deprecate https://drupal.org/node/2060773
   if (isset($info['preprocess functions'])) {
     foreach ($info['preprocess functions'] as $preprocessor_function) {
       if (function_exists($preprocessor_function)) {
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 92cf2a4..96b332f 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -273,6 +273,31 @@ public function invoke($module, $hook, $args = array()) {
   }
 
   /**
+   * Rudimentary and temporary method for invoking theme hooks.
+   *
+   * @todo Remove once a proper Drupal::themeHandler() is created.
+   * @see https://drupal.org/node/2029819
+   */
+  public function themeInvoke($hook, $args = array()) {
+    // Allow the active theme to invoke hooks after the theme system has been
+    // initialized.
+    global $theme, $base_theme_info;
+    if (isset($theme)) {
+      $theme_keys = array();
+      foreach ($base_theme_info as $base) {
+        $theme_keys[] = $base->name;
+      }
+      $theme_keys[] = $theme;
+      foreach ($theme_keys as $theme_key) {
+        $function = $theme_key . '_' . $hook;
+        if (function_exists($function)) {
+          return call_user_func_array($function, $args);
+        }
+      }
+    }
+  }
+
+  /**
    * Implements \Drupal\Core\Extension\ModuleHandlerInterface::invokeAll().
    */
   public function invokeAll($hook, $args = array()) {
diff --git a/core/lib/Drupal/Core/Extension/UpdateModuleHandler.php b/core/lib/Drupal/Core/Extension/UpdateModuleHandler.php
index 7d7ac9c..34c6793 100644
--- a/core/lib/Drupal/Core/Extension/UpdateModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/UpdateModuleHandler.php
@@ -24,6 +24,10 @@ public function getImplementations($hook) {
     if (substr($hook, -6) === '_alter') {
       return array();
     }
+    // Some system_theme() hooks need to be prepared.
+    if (substr($hook, 0, 13) === 'theme_prepare') {
+      return array('system');
+    }
     // theme() is called during updates and fires hooks, so whitelist the
     // system module.
     if (substr($hook, 0, 6) == 'theme_') {
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..d8a7451
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemePrepareTest.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Theme\ThemeTest.
+ */
+
+namespace Drupal\system\Tests\Theme;
+
+use Drupal\simpletest\WebTestBase;
+use Drupal\test_theme\ThemeClass;
+
+/**
+ * Tests low-level theme functions.
+ */
+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',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+    theme_enable(array('test_theme'));
+  }
+
+  function testThemePrepare() {
+    $this->drupalGet('theme-test/prepare', array('query' => array('XDEBUG_SESSION_START' => 'PHPSTORM')));
+    $this->assertText('Inserted variable1 via hook_theme_prepare().', 'Inserted variable1 via hook_theme_prepare().');
+    $this->assertText('Inserted variable2 via hook_theme_prepare_BASE_THEME_ID().', 'Inserted variable2 via hook_theme_prepare_BASE_THEME_ID().');
+    $this->assertText('Inserted themeVariable1 via theme hook_theme_prepare().', 'Inserted themeVariable1 via theme hook_theme_prepare().');
+    $this->assertText('Inserted themeVariable2 via theme hook_theme_prepare_BASE_THEME_ID().', 'Inserted themeVariable2 via theme hook_theme_prepare_BASE_THEME_ID().');
+  }
+
+  function testThemePrepareAlter() {
+    $this->drupalGet('theme-test/prepare-alter', array('query' => array('XDEBUG_SESSION_START' => 'PHPSTORM')));
+    $this->assertText('Inserted variable1 via hook_theme_prepare() was altered by hook_theme_prepare_alter().', 'Inserted variable1 via hook_theme_prepare() was altered by hook_theme_prepare_alter().');
+    $this->assertText('Inserted variable2 via hook_theme_prepare_BASE_THEME_ID() was altered by hook_theme_prepare_BASE_THEME_ID_alter().', 'Inserted variable2 via hook_theme_prepare_BASE_THEME_ID() was altered by hook_theme_prepare_BASE_THEME_ID_alter().');
+    $this->assertText('Inserted themeVariable1 via theme hook_theme_prepare() was altered by theme hook_theme_prepare_alter().', 'Inserted themeVariable1 via theme hook_theme_prepare() was altered by theme hook_theme_prepare_alter().');
+    $this->assertText('Inserted themeVariable2 via theme hook_theme_prepare_BASE_THEME_ID() was altered by theme hook_theme_prepare_BASE_THEME_ID_alter().', 'Inserted themeVariable2 via theme hook_theme_prepare_BASE_THEME_ID() was altered by theme hook_theme_prepare_BASE_THEME_ID_alter().');
+  }
+
+}
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 8da6c62..32231ff 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
@@ -120,4 +120,16 @@ function functionSuggestionAlter() {
     return array('#theme' => 'theme_test_function_suggestions');
   }
 
+  function prepare() {
+    return array(
+      '#theme' => 'theme_test_prepare_variable',
+    );
+  }
+
+  function prepareAlter() {
+    return array(
+      '#theme' => 'theme_test_prepare_alter_variable',
+    );
+  }
+
 }
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 badea81..43872ad 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.module
+++ b/core/modules/system/tests/modules/theme_test/theme_test.module
@@ -42,6 +42,12 @@ function theme_test_theme($existing, $type, $theme, $path) {
   $items['theme_test_function_template_override'] = array(
     'variables' => array(),
   );
+  $items['theme_test_prepare_variable'] = array(
+    'variables' => array('element' => array()),
+  );
+  $items['theme_test_prepare_alter_variable'] = array(
+    'variables' => array('element' => array()),
+  );
   return $items;
 }
 
@@ -74,6 +80,14 @@ 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',
+  );
+  $items['theme-test/prepare-alter'] = array(
+    'theme callback' => '_theme_custom_theme',
+    'route_name' => 'theme_test.prepare_alter',
+  );
   return $items;
 }
 /**
@@ -166,3 +180,68 @@ function theme_theme_test_function_suggestions($variables) {
 function theme_test_theme_suggestions_theme_test_suggestion_provided(array $variables) {
   return array('theme_test_suggestion_provided__' . 'foo');
 }
+
+function theme_theme_test_prepare_variable($variables) {
+  return drupal_render($variables['element']);
+}
+
+function theme_theme_test_prepare_alter_variable($variables) {
+  return drupal_render($variables['element']);
+}
+
+/**
+ * Implements hook_theme_prepare().
+ */
+function theme_test_theme_prepare($context) {
+  $variables = array();
+  if ($context['#base_theme_id'] == 'theme_test_prepare_variable' || $context['#base_theme_id'] == 'theme_test_prepare_alter_variable') {
+    $variables['element'] = array(
+      'variable1' => array(
+        '#markup' => '<p>Inserted variable1 via hook_theme_prepare().</p>',
+      ),
+    );
+  }
+  return $variables;
+}
+
+/**
+ * Implements hook_theme_prepare_BASE_THEME_ID().
+ */
+function theme_test_theme_prepare_theme_test_prepare_variable() {
+  $variables = array();
+  $variables['element'] = array(
+    'variable2' => array(
+      '#markup' => '<p>Inserted variable2 via hook_theme_prepare_BASE_THEME_ID().</p>',
+    ),
+  );
+  return $variables;
+}
+
+/**
+ * Implements hook_theme_prepare_BASE_THEME_ID().
+ */
+function theme_test_theme_prepare_theme_test_prepare_alter_variable() {
+  $variables = array();
+  $variables['element'] = array(
+    'variable2' => array(
+      '#markup' => '<p>Inserted variable2 via hook_theme_prepare_BASE_THEME_ID().</p>',
+    ),
+  );
+  return $variables;
+}
+
+/**
+ * Implements hook_theme_prepare_alter().
+ */
+function theme_test_theme_prepare_alter(&$variables, $context) {
+  if ($context['#base_theme_id'] == 'theme_test_prepare_alter_variable') {
+    $variables['element']['variable1']['#markup'] = '<p>Inserted variable1 via hook_theme_prepare() was altered by hook_theme_prepare_alter().</p>';
+  }
+}
+
+/**
+ * Implements hook_theme_prepare_BASE_THEME_ID_alter().
+ */
+function theme_test_theme_prepare_theme_test_prepare_alter_variable_alter(&$variables) {
+  $variables['element']['variable2']['#markup'] = '<p>Inserted variable2 via hook_theme_prepare_BASE_THEME_ID() was altered by hook_theme_prepare_BASE_THEME_ID_alter().</p>';
+}
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 b4a7fd6..446f780 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
@@ -69,3 +69,17 @@ function_suggestion_alter:
     _content: '\Drupal\theme_test\ThemeTestController::functionSuggestionAlter'
   requirements:
     _permission: 'access content'
+
+theme_test.prepare:
+  pattern: '/theme-test/prepare'
+  defaults:
+    _content: '\Drupal\theme_test\ThemeTestController::prepare'
+  requirements:
+    _permission: 'access content'
+
+theme_test.prepare_alter:
+  pattern: '/theme-test/prepare-alter'
+  defaults:
+    _content: '\Drupal\theme_test\ThemeTestController::prepareAlter'
+  requirements:
+    _permission: 'access content'
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 9b10b2b..5f07789 100644
--- a/core/modules/system/tests/themes/test_theme/test_theme.theme
+++ b/core/modules/system/tests/themes/test_theme/test_theme.theme
@@ -69,3 +69,60 @@ 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().
+ */
+function test_theme_theme_prepare($context) {
+  $variables = array();
+  if ($context['#base_theme_id'] == 'theme_test_prepare_variable' || $context['#base_theme_id'] == 'theme_test_prepare_alter_variable') {
+    $variables['element'] = array(
+      'themeVariable1' => array(
+        '#markup' => '<p>Inserted themeVariable1 via theme hook_theme_prepare().</p>',
+      ),
+    );
+  }
+  return $variables;
+}
+
+/**
+ * Implements hook_theme_prepare_BASE_THEME_ID().
+ */
+function test_theme_theme_prepare_theme_test_prepare_variable() {
+  $variables = array();
+  $variables['element'] = array(
+    'themeVariable2' => array(
+      '#markup' => '<p>Inserted themeVariable2 via theme hook_theme_prepare_BASE_THEME_ID().</p>',
+    ),
+  );
+  return $variables;
+}
+
+/**
+ * Implements hook_theme_prepare_BASE_THEME_ID().
+ */
+function test_theme_theme_prepare_theme_test_prepare_alter_variable() {
+  $variables = array();
+  $variables['element'] = array(
+    'themeVariable2' => array(
+      '#markup' => '<p>Inserted themeVariable2 via theme hook_theme_prepare_BASE_THEME_ID().</p>',
+    ),
+  );
+  return $variables;
+}
+
+/**
+ * Implements hook_theme_prepare_alter().
+ */
+function test_theme_theme_prepare_alter(&$variables, $context) {
+  if ($context['#base_theme_id'] == 'theme_test_prepare_alter_variable') {
+    $variables['element']['themeVariable1']['#markup'] = '<p>Inserted themeVariable1 via theme hook_theme_prepare() was altered by theme hook_theme_prepare_alter().</p>';
+  }
+}
+
+/**
+ * Implements hook_theme_prepare_BASE_THEME_ID_alter().
+ */
+function test_theme_theme_prepare_theme_test_prepare_alter_variable_alter(&$variables) {
+  $variables['element']['themeVariable2']['#markup'] = '<p>Inserted themeVariable2 via theme hook_theme_prepare_BASE_THEME_ID() was altered by theme hook_theme_prepare_BASE_THEME_ID_alter().</p>';
+}
