diff --git a/core/core.services.yml b/core/core.services.yml
index d44bcb1ca77e455235d6df653d408e6ffad0d8d7..9b1ab37b365ace3e68e98c2feee262d32244f385 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -434,7 +434,10 @@ services:
     lazy: true
   theme_handler:
     class: Drupal\Core\Extension\ThemeHandler
-    arguments: ['@app.root', '@config.factory', '@module_handler', '@state', '@info_parser', '@logger.channel.default', '@asset.css.collection_optimizer', '@config.installer', '@config.manager', '@router.builder']
+    arguments: ['@app.root', '@config.factory', '@module_handler', '@state', '@info_parser']
+  theme_installer:
+    class: Drupal\Core\Extension\ThemeInstaller
+    arguments: ['@theme_handler', '@config.factory', '@config.installer', '@module_handler', '@config.manager', '@asset.css.collection_optimizer', '@router.builder', '@logger.channel.default', '@state']
   entity.manager:
     class: Drupal\Core\Entity\EntityManager
     arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation', '@class_resolver', '@typed_data_manager', '@keyvalue', '@event_dispatcher']
diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php
index 6684c72f17f0ad80a5de2b61a4b0d6982818c6a2..a6baf9f7ba2e4e3685fadf7e086a251c76f5cf6a 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandler.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php
@@ -8,15 +8,8 @@
 namespace Drupal\Core\Extension;
 
 use Drupal\Component\Utility\SafeMarkup;
-use Drupal\Core\Asset\AssetCollectionOptimizerInterface;
-use Drupal\Core\Cache\Cache;
 use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Core\Config\ConfigInstallerInterface;
-use Drupal\Core\Config\ConfigManagerInterface;
-use Drupal\Core\Config\PreExistingConfigException;
-use Drupal\Core\Routing\RouteBuilderInterface;
 use Drupal\Core\State\StateInterface;
-use Psr\Log\LoggerInterface;
 
 /**
  * Default theme handler using the config system to store installation statuses.
@@ -135,33 +128,15 @@ class ThemeHandler implements ThemeHandlerInterface {
    *   The state store.
    * @param \Drupal\Core\Extension\InfoParserInterface $info_parser
    *   The info parser to parse the theme.info.yml files.
-   * @param \Psr\Log\LoggerInterface $logger
-   *   A logger instance.
-   * @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $css_collection_optimizer
-   *   The CSS asset collection optimizer service.
-   * @param \Drupal\Core\Config\ConfigInstallerInterface $config_installer
-   *   (optional) The config installer to install configuration. This optional
-   *   to allow the theme handler to work before Drupal is installed and has a
-   *   database.
-   * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
-   *   The config manager used to uninstall a theme.
-   * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
-   *   (optional) The route builder service to rebuild the routes if a theme is
-   *   installed.
    * @param \Drupal\Core\Extension\ExtensionDiscovery $extension_discovery
    *   (optional) A extension discovery instance (for unit tests).
    */
-  public function __construct($root, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, InfoParserInterface $info_parser,LoggerInterface $logger, AssetCollectionOptimizerInterface $css_collection_optimizer = NULL, ConfigInstallerInterface $config_installer = NULL, ConfigManagerInterface $config_manager = NULL, RouteBuilderInterface $route_builder = NULL, ExtensionDiscovery $extension_discovery = NULL) {
+  public function __construct($root, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, InfoParserInterface $info_parser, ExtensionDiscovery $extension_discovery = NULL) {
     $this->root = $root;
     $this->configFactory = $config_factory;
     $this->moduleHandler = $module_handler;
     $this->state = $state;
     $this->infoParser = $info_parser;
-    $this->logger = $logger;
-    $this->cssCollectionOptimizer = $css_collection_optimizer;
-    $this->configInstaller = $config_installer;
-    $this->configManager = $config_manager;
-    $this->routeBuilder = $route_builder;
     $this->extensionDiscovery = $extension_discovery;
   }
 
@@ -190,183 +165,18 @@ public function setDefault($name) {
    * {@inheritdoc}
    */
   public function install(array $theme_list, $install_dependencies = TRUE) {
-    $extension_config = $this->configFactory->getEditable('core.extension');
-
-    $theme_data = $this->rebuildThemeData();
-
-    if ($install_dependencies) {
-      $theme_list = array_combine($theme_list, $theme_list);
-
-      if ($missing = array_diff_key($theme_list, $theme_data)) {
-        // One or more of the given themes doesn't exist.
-        throw new \InvalidArgumentException(SafeMarkup::format('Unknown themes: !themes.', array(
-          '!themes' => implode(', ', $missing),
-        )));
-      }
-
-      // Only process themes that are not installed currently.
-      $installed_themes = $extension_config->get('theme') ?: array();
-      if (!$theme_list = array_diff_key($theme_list, $installed_themes)) {
-        // Nothing to do. All themes already installed.
-        return TRUE;
-      }
-
-      while (list($theme) = each($theme_list)) {
-        // Add dependencies to the list. The new themes will be processed as
-        // the while loop continues.
-        foreach (array_keys($theme_data[$theme]->requires) as $dependency) {
-          if (!isset($theme_data[$dependency])) {
-            // The dependency does not exist.
-            return FALSE;
-          }
-
-          // Skip already installed themes.
-          if (!isset($theme_list[$dependency]) && !isset($installed_themes[$dependency])) {
-            $theme_list[$dependency] = $dependency;
-          }
-        }
-      }
-
-      // Set the actual theme weights.
-      $theme_list = array_map(function ($theme) use ($theme_data) {
-        return $theme_data[$theme]->sort;
-      }, $theme_list);
-
-      // Sort the theme list by their weights (reverse).
-      arsort($theme_list);
-      $theme_list = array_keys($theme_list);
-    }
-    else {
-      $installed_themes = $extension_config->get('theme') ?: array();
-    }
-
-    $themes_installed = array();
-    foreach ($theme_list as $key) {
-      // Only process themes that are not already installed.
-      $installed = $extension_config->get("theme.$key") !== NULL;
-      if ($installed) {
-        continue;
-      }
-
-      // Throw an exception if the theme name is too long.
-      if (strlen($key) > DRUPAL_EXTENSION_NAME_MAX_LENGTH) {
-        throw new ExtensionNameLengthException(SafeMarkup::format('Theme name %name is over the maximum allowed length of @max characters.', array(
-          '%name' => $key,
-          '@max' => DRUPAL_EXTENSION_NAME_MAX_LENGTH,
-        )));
-      }
-
-      // Validate default configuration of the theme. If there is existing
-      // configuration then stop installing.
-      $this->configInstaller->checkConfigurationToInstall('theme', $key);
-
-      // The value is not used; the weight is ignored for themes currently. Do
-      // not check schema when saving the configuration.
-      $extension_config
-        ->set("theme.$key", 0)
-        ->save(TRUE);
-
-      // Add the theme to the current list.
-      // @todo Remove all code that relies on $status property.
-      $theme_data[$key]->status = 1;
-      $this->addTheme($theme_data[$key]);
-
-      // Update the current theme data accordingly.
-      $current_theme_data = $this->state->get('system.theme.data', array());
-      $current_theme_data[$key] = $theme_data[$key];
-      $this->state->set('system.theme.data', $current_theme_data);
-
-      // Reset theme settings.
-      $theme_settings = &drupal_static('theme_get_setting');
-      unset($theme_settings[$key]);
-
-      // @todo Remove system_list().
-      $this->systemListReset();
-
-      // Only install default configuration if this theme has not been installed
-      // already.
-      if (!isset($installed_themes[$key])) {
-        // Install default configuration of the theme.
-        $this->configInstaller->installDefaultConfig('theme', $key);
-      }
-
-      $themes_installed[] = $key;
-
-      // Record the fact that it was installed.
-      $this->logger->info('%theme theme installed.', array('%theme' => $key));
-    }
-
-    $this->cssCollectionOptimizer->deleteAll();
-    $this->resetSystem();
-
-    // Invoke hook_themes_installed() after the themes have been installed.
-    $this->moduleHandler->invokeAll('themes_installed', array($themes_installed));
-
-    return !empty($themes_installed);
+    // We keep the old install() method as BC layer but redirect directly to the
+    // theme installer.
+    \Drupal::service('theme_installer')->install($theme_list, $install_dependencies);
   }
 
   /**
    * {@inheritdoc}
    */
   public function uninstall(array $theme_list) {
-    $extension_config = $this->configFactory->getEditable('core.extension');
-    $theme_config = $this->configFactory->getEditable('system.theme');
-    $list = $this->listInfo();
-    foreach ($theme_list as $key) {
-      if (!isset($list[$key])) {
-        throw new \InvalidArgumentException("Unknown theme: $key.");
-      }
-      if ($key === $theme_config->get('default')) {
-        throw new \InvalidArgumentException("The current default theme $key cannot be uninstalled.");
-      }
-      if ($key === $theme_config->get('admin')) {
-        throw new \InvalidArgumentException("The current admin theme $key cannot be uninstalled.");
-      }
-      // Base themes cannot be uninstalled if sub themes are installed, and if
-      // they are not uninstalled at the same time.
-      // @todo https://www.drupal.org/node/474684 and
-      //   https://www.drupal.org/node/1297856 themes should leverage the module
-      //   dependency system.
-      if (!empty($list[$key]->sub_themes)) {
-        foreach ($list[$key]->sub_themes as $sub_key => $sub_label) {
-          if (isset($list[$sub_key]) && !in_array($sub_key, $theme_list, TRUE)) {
-            throw new \InvalidArgumentException("The base theme $key cannot be uninstalled, because theme $sub_key depends on it.");
-          }
-        }
-      }
-    }
-
-    $this->cssCollectionOptimizer->deleteAll();
-    $current_theme_data = $this->state->get('system.theme.data', array());
-    foreach ($theme_list as $key) {
-      // The value is not used; the weight is ignored for themes currently.
-      $extension_config->clear("theme.$key");
-
-      // Remove the theme from the current list.
-      unset($this->list[$key]);
-
-      // Update the current theme data accordingly.
-      unset($current_theme_data[$key]);
-
-      // Reset theme settings.
-      $theme_settings = &drupal_static('theme_get_setting');
-      unset($theme_settings[$key]);
-
-      // @todo Remove system_list().
-      $this->systemListReset();
-
-      // Remove all configuration belonging to the theme.
-      $this->configManager->uninstall('theme', $key);
-
-    }
-    // Don't check schema when uninstalling a theme since we are only clearing
-    // keys.
-    $extension_config->save(TRUE);
-    $this->state->set('system.theme.data', $current_theme_data);
-
-    $this->resetSystem();
-
-    $this->moduleHandler->invokeAll('themes_uninstalled', [$theme_list]);
+    // We keep the old uninstall() method as BC layer but redirect directly to
+    // the theme installer.
+    \Drupal::service('theme_installer')->uninstall($theme_list);
   }
 
   /**
@@ -614,21 +424,6 @@ protected function getExtensionDiscovery() {
     return $this->extensionDiscovery;
   }
 
-  /**
-   * Resets some other systems like rebuilding the route information or caches.
-   */
-  protected function resetSystem() {
-    if ($this->routeBuilder) {
-      $this->routeBuilder->setRebuildNeeded();
-    }
-    $this->systemListReset();
-
-    // @todo It feels wrong to have the requirement to clear the local tasks
-    //   cache here.
-    Cache::invalidateTags(array('local_task'));
-    $this->themeRegistryRebuild();
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -647,13 +442,6 @@ protected function systemListReset() {
     system_list_reset();
   }
 
-  /**
-   * Wraps drupal_theme_rebuild().
-   */
-  protected function themeRegistryRebuild() {
-    drupal_theme_rebuild();
-  }
-
   /**
    * Wraps system_list().
    *
diff --git a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
index aa198d64ac23dd35cc2aaddb822ae43b2496c00e..5fdceafda5dfdaf2cd2a1c199053574a301f5257 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
@@ -8,7 +8,7 @@
 namespace Drupal\Core\Extension;
 
 /**
- * Manages the list of available themes as well as install/uninstall them.
+ * Manages the list of available themes.
  */
 interface ThemeHandlerInterface {
 
@@ -27,6 +27,11 @@ interface ThemeHandlerInterface {
    *
    * @throws \Drupal\Core\Extension\ExtensionNameLengthException
    *   Thrown when the theme name is to long
+   *
+   * @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0.
+   *   Use the theme_installer service instead.
+   *
+   * @see \Drupal\Core\Extension\ThemeInstallerInterface::install
    */
   public function install(array $theme_list, $install_dependencies = TRUE);
 
@@ -43,6 +48,11 @@ public function install(array $theme_list, $install_dependencies = TRUE);
    *   Thrown when you uninstall an not installed theme.
    *
    * @see hook_themes_uninstalled()
+   *
+   * @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 9.0.0.
+   *   Use the theme_installer service instead.
+   *
+   * @see \Drupal\Core\Extension\ThemeInstallerInterface::install
    */
   public function uninstall(array $theme_list);
 
@@ -87,6 +97,15 @@ public function uninstall(array $theme_list);
    */
   public function listInfo();
 
+
+  /**
+   * Adds a theme extension to the internal listing.
+   *
+   * @param \Drupal\Core\Extension\Extension $theme
+   *   The theme extension.
+   */
+  public function addTheme(Extension $theme);
+
   /**
    * Refreshes the theme info data of currently installed themes.
    *
diff --git a/core/lib/Drupal/Core/Extension/ThemeInstaller.php b/core/lib/Drupal/Core/Extension/ThemeInstaller.php
new file mode 100644
index 0000000000000000000000000000000000000000..1bebca2dbd3c61c784486e6405a878be0bb76aa9
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/ThemeInstaller.php
@@ -0,0 +1,312 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Extension\ThemeInstaller.
+ */
+
+namespace Drupal\Core\Extension;
+
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Asset\AssetCollectionOptimizerInterface;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\ConfigInstallerInterface;
+use Drupal\Core\Config\ConfigManagerInterface;
+use Drupal\Core\Routing\RouteBuilderInterface;
+use Drupal\Core\State\StateInterface;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Manages theme installation/uninstallation.
+ */
+class ThemeInstaller implements ThemeInstallerInterface {
+
+  /**
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * @var \Drupal\Core\Config\ConfigInstallerInterface
+   */
+  protected $configInstaller;
+
+  /**
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * @var \Drupal\Core\Config\ConfigManagerInterface
+   */
+  protected $configManager;
+
+  /**
+   * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface
+   */
+  protected $cssCollectionOptimizer;
+
+  /**
+   * @var \Drupal\Core\Routing\RouteBuilderInterface
+   */
+  protected $routeBuilder;
+
+  /**
+   * @var \Psr\Log\LoggerInterface
+   */
+  protected $logger;
+
+
+  /**
+   * Constructs a new ThemeInstaller.
+   *
+   * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
+   *   The theme handler.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory to get the installed themes.
+   * @param \Drupal\Core\Config\ConfigInstallerInterface $config_installer
+   *   (optional) The config installer to install configuration. This optional
+   *   to allow the theme handler to work before Drupal is installed and has a
+   *   database.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to fire themes_installed/themes_uninstalled hooks.
+   * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
+   *   The config manager used to uninstall a theme.
+   * @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $css_collection_optimizer
+   *   The CSS asset collection optimizer service.
+   * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
+   *   (optional) The route builder service to rebuild the routes if a theme is
+   *   installed.
+   * @param \Psr\Log\LoggerInterface $logger
+   *   A logger instance.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state store.
+   */
+  public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryInterface $config_factory, ConfigInstallerInterface $config_installer, ModuleHandlerInterface $module_handler, ConfigManagerInterface $config_manager, AssetCollectionOptimizerInterface $css_collection_optimizer, RouteBuilderInterface $route_builder, LoggerInterface $logger, StateInterface $state) {
+    $this->themeHandler = $theme_handler;
+    $this->configFactory = $config_factory;
+    $this->configInstaller = $config_installer;
+    $this->moduleHandler = $module_handler;
+    $this->configManager = $config_manager;
+    $this->cssCollectionOptimizer = $css_collection_optimizer;
+    $this->routeBuilder = $route_builder;
+    $this->logger = $logger;
+    $this->state = $state;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function install(array $theme_list, $install_dependencies = TRUE) {
+    $extension_config = $this->configFactory->getEditable('core.extension');
+
+    $theme_data = $this->themeHandler->rebuildThemeData();
+
+    if ($install_dependencies) {
+      $theme_list = array_combine($theme_list, $theme_list);
+
+      if ($missing = array_diff_key($theme_list, $theme_data)) {
+        // One or more of the given themes doesn't exist.
+        throw new \InvalidArgumentException(SafeMarkup::format('Unknown themes: !themes.', array(
+          '!themes' => implode(', ', $missing),
+        )));
+      }
+
+      // Only process themes that are not installed currently.
+      $installed_themes = $extension_config->get('theme') ?: array();
+      if (!$theme_list = array_diff_key($theme_list, $installed_themes)) {
+        // Nothing to do. All themes already installed.
+        return TRUE;
+      }
+
+      while (list($theme) = each($theme_list)) {
+        // Add dependencies to the list. The new themes will be processed as
+        // the while loop continues.
+        foreach (array_keys($theme_data[$theme]->requires) as $dependency) {
+          if (!isset($theme_data[$dependency])) {
+            // The dependency does not exist.
+            return FALSE;
+          }
+
+          // Skip already installed themes.
+          if (!isset($theme_list[$dependency]) && !isset($installed_themes[$dependency])) {
+            $theme_list[$dependency] = $dependency;
+          }
+        }
+      }
+
+      // Set the actual theme weights.
+      $theme_list = array_map(function ($theme) use ($theme_data) {
+        return $theme_data[$theme]->sort;
+      }, $theme_list);
+
+      // Sort the theme list by their weights (reverse).
+      arsort($theme_list);
+      $theme_list = array_keys($theme_list);
+    }
+    else {
+      $installed_themes = $extension_config->get('theme') ?: array();
+    }
+
+    $themes_installed = array();
+    foreach ($theme_list as $key) {
+      // Only process themes that are not already installed.
+      $installed = $extension_config->get("theme.$key") !== NULL;
+      if ($installed) {
+        continue;
+      }
+
+      // Throw an exception if the theme name is too long.
+      if (strlen($key) > DRUPAL_EXTENSION_NAME_MAX_LENGTH) {
+        throw new ExtensionNameLengthException(SafeMarkup::format('Theme name %name is over the maximum allowed length of @max characters.', array(
+          '%name' => $key,
+          '@max' => DRUPAL_EXTENSION_NAME_MAX_LENGTH,
+        )));
+      }
+
+      // Validate default configuration of the theme. If there is existing
+      // configuration then stop installing.
+      $this->configInstaller->checkConfigurationToInstall('theme', $key);
+
+      // The value is not used; the weight is ignored for themes currently. Do
+      // not check schema when saving the configuration.
+      $extension_config
+        ->set("theme.$key", 0)
+        ->save(TRUE);
+
+      // Add the theme to the current list.
+      // @todo Remove all code that relies on $status property.
+      $theme_data[$key]->status = 1;
+      $this->themeHandler->addTheme($theme_data[$key]);
+
+      // Update the current theme data accordingly.
+      $current_theme_data = $this->state->get('system.theme.data', array());
+      $current_theme_data[$key] = $theme_data[$key];
+      $this->state->set('system.theme.data', $current_theme_data);
+
+      // Reset theme settings.
+      $theme_settings = &drupal_static('theme_get_setting');
+      unset($theme_settings[$key]);
+
+      // @todo Remove system_list().
+      $this->systemListReset();
+
+      // Only install default configuration if this theme has not been installed
+      // already.
+      if (!isset($installed_themes[$key])) {
+        // Install default configuration of the theme.
+        $this->configInstaller->installDefaultConfig('theme', $key);
+      }
+
+      $themes_installed[] = $key;
+
+      // Record the fact that it was installed.
+      $this->logger->info('%theme theme installed.', array('%theme' => $key));
+    }
+
+    $this->cssCollectionOptimizer->deleteAll();
+    $this->resetSystem();
+
+    // Invoke hook_themes_installed() after the themes have been installed.
+    $this->moduleHandler->invokeAll('themes_installed', array($themes_installed));
+
+    return !empty($themes_installed);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function uninstall(array $theme_list) {
+    $extension_config = $this->configFactory->getEditable('core.extension');
+    $theme_config = $this->configFactory->getEditable('system.theme');
+    $list = $this->themeHandler->listInfo();
+    foreach ($theme_list as $key) {
+      if (!isset($list[$key])) {
+        throw new \InvalidArgumentException("Unknown theme: $key.");
+      }
+      if ($key === $theme_config->get('default')) {
+        throw new \InvalidArgumentException("The current default theme $key cannot be uninstalled.");
+      }
+      if ($key === $theme_config->get('admin')) {
+        throw new \InvalidArgumentException("The current admin theme $key cannot be uninstalled.");
+      }
+      // Base themes cannot be uninstalled if sub themes are installed, and if
+      // they are not uninstalled at the same time.
+      // @todo https://www.drupal.org/node/474684 and
+      //   https://www.drupal.org/node/1297856 themes should leverage the module
+      //   dependency system.
+      if (!empty($list[$key]->sub_themes)) {
+        foreach ($list[$key]->sub_themes as $sub_key => $sub_label) {
+          if (isset($list[$sub_key]) && !in_array($sub_key, $theme_list, TRUE)) {
+            throw new \InvalidArgumentException("The base theme $key cannot be uninstalled, because theme $sub_key depends on it.");
+          }
+        }
+      }
+    }
+
+    $this->cssCollectionOptimizer->deleteAll();
+    $current_theme_data = $this->state->get('system.theme.data', array());
+    foreach ($theme_list as $key) {
+      // The value is not used; the weight is ignored for themes currently.
+      $extension_config->clear("theme.$key");
+
+      // Update the current theme data accordingly.
+      unset($current_theme_data[$key]);
+
+      // Reset theme settings.
+      $theme_settings = &drupal_static('theme_get_setting');
+      unset($theme_settings[$key]);
+
+      // Remove all configuration belonging to the theme.
+      $this->configManager->uninstall('theme', $key);
+
+    }
+    // Don't check schema when uninstalling a theme since we are only clearing
+    // keys.
+    $extension_config->save(TRUE);
+    $this->state->set('system.theme.data', $current_theme_data);
+
+
+    // @todo Remove system_list().
+    $this->themeHandler->refreshInfo();
+    $this->resetSystem();
+
+    $this->moduleHandler->invokeAll('themes_uninstalled', [$theme_list]);
+  }
+
+  /**
+   * Resets some other systems like rebuilding the route information or caches.
+   */
+  protected function resetSystem() {
+    if ($this->routeBuilder) {
+      $this->routeBuilder->setRebuildNeeded();
+    }
+    $this->systemListReset();
+
+    // @todo It feels wrong to have the requirement to clear the local tasks
+    //   cache here.
+    Cache::invalidateTags(array('local_task'));
+    $this->themeRegistryRebuild();
+  }
+
+  /**
+   * Wraps drupal_theme_rebuild().
+   */
+  protected function themeRegistryRebuild() {
+    drupal_theme_rebuild();
+  }
+
+  /**
+   * Wraps system_list_reset().
+   */
+  protected function systemListReset() {
+    system_list_reset();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Extension/ThemeInstallerInterface.php b/core/lib/Drupal/Core/Extension/ThemeInstallerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..192979abd795815fa676a0c440809e175031ad02
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/ThemeInstallerInterface.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Extension\ThemeInstallerInterface.
+ */
+
+namespace Drupal\Core\Extension;
+
+/**
+ * Manages theme installation/uninstallation.
+ */
+interface ThemeInstallerInterface {
+
+  /**
+   * Installs a given list of themes.
+   *
+   * @param array $theme_list
+   *   An array of theme names.
+   * @param bool $install_dependencies
+   *   (optional) If TRUE, dependencies will automatically be installed in the
+   *   correct order. This incurs a significant performance cost, so use FALSE
+   *   if you know $theme_list is already complete and in the correct order.
+   *
+   * @return bool
+   *   Whether any of the given themes have been installed.
+   *
+   * @throws \Drupal\Core\Extension\ExtensionNameLengthException
+   *   Thrown when the theme name is to long
+   */
+  public function install(array $theme_list, $install_dependencies = TRUE);
+
+  /**
+   * Uninstalls a given list of themes.
+   *
+   * Uninstalling a theme removes all related configuration (like blocks) and
+   * invokes the 'themes_uninstalled' hook.
+   *
+   * @param array $theme_list
+   *   The themes to uninstall.
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown when you uninstall an not installed theme.
+   *
+   * @see hook_themes_uninstalled()
+   */
+  public function uninstall(array $theme_list);
+
+}
diff --git a/core/modules/system/src/Tests/Extension/ThemeHandlerTest.php b/core/modules/system/src/Tests/Extension/ThemeInstallerTest.php
similarity index 89%
rename from core/modules/system/src/Tests/Extension/ThemeHandlerTest.php
rename to core/modules/system/src/Tests/Extension/ThemeInstallerTest.php
index e5fb3f4cf7f948d30815e702bf1ae2cc085eb7a7..07a306c6972b834a87328cd793a3f26d14a6a9f2 100644
--- a/core/modules/system/src/Tests/Extension/ThemeHandlerTest.php
+++ b/core/modules/system/src/Tests/Extension/ThemeInstallerTest.php
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Contains \Drupal\system\Tests\Extension\ThemeHandlerTest.
+ * Contains \Drupal\system\Tests\Extension\ThemeInstallerTest.
  */
 
 namespace Drupal\system\Tests\Extension;
@@ -16,7 +16,7 @@
  *
  * @group Extension
  */
-class ThemeHandlerTest extends KernelTestBase {
+class ThemeInstallerTest extends KernelTestBase {
 
   /**
    * Modules to enable.
@@ -63,7 +63,7 @@ function testInstall() {
     $themes = $this->themeHandler()->listInfo();
     $this->assertFalse(isset($themes[$name]));
 
-    $this->themeHandler()->install(array($name));
+    $this->themeInstaller()->install(array($name));
 
     $this->assertIdentical($this->extensionConfig()->get("theme.$name"), 0);
 
@@ -89,13 +89,13 @@ function testInstallSubTheme() {
     $themes = $this->themeHandler()->listInfo();
     $this->assertFalse(array_keys($themes));
 
-    $this->themeHandler()->install(array($name));
+    $this->themeInstaller()->install(array($name));
 
     $themes = $this->themeHandler()->listInfo();
     $this->assertTrue(isset($themes[$name]));
     $this->assertTrue(isset($themes[$base_name]));
 
-    $this->themeHandler()->uninstall(array($name));
+    $this->themeInstaller()->uninstall(array($name));
 
     $themes = $this->themeHandler()->listInfo();
     $this->assertFalse(isset($themes[$name]));
@@ -113,7 +113,7 @@ function testInstallNonExisting() {
 
     try {
       $message = 'ThemeHandler::install() throws InvalidArgumentException upon installing a non-existing theme.';
-      $this->themeHandler()->install(array($name));
+      $this->themeInstaller()->install(array($name));
       $this->fail($message);
     }
     catch (\InvalidArgumentException $e) {
@@ -132,7 +132,7 @@ function testInstallNameTooLong() {
 
     try {
       $message = 'ThemeHandler::install() throws ExtensionNameLengthException upon installing a theme with a too long name.';
-      $this->themeHandler()->install(array($name));
+      $this->themeInstaller()->install(array($name));
       $this->fail($message);
     }
     catch (ExtensionNameLengthException $e) {
@@ -146,7 +146,7 @@ function testInstallNameTooLong() {
   function testUninstallDefault() {
     $name = 'stark';
     $other_name = 'bartik';
-    $this->themeHandler()->install(array($name, $other_name));
+    $this->themeInstaller()->install(array($name, $other_name));
     $this->themeHandler()->setDefault($name);
 
     $themes = $this->themeHandler()->listInfo();
@@ -173,7 +173,7 @@ function testUninstallDefault() {
   function testUninstallAdmin() {
     $name = 'stark';
     $other_name = 'bartik';
-    $this->themeHandler()->install(array($name, $other_name));
+    $this->themeInstaller()->install(array($name, $other_name));
     $this->config('system.theme')->set('admin', $name)->save();
 
     $themes = $this->themeHandler()->listInfo();
@@ -201,8 +201,8 @@ function testUninstallSubTheme() {
     $name = 'test_subtheme';
     $base_name = 'test_basetheme';
 
-    $this->themeHandler()->install(array($name));
-    $this->themeHandler()->uninstall(array($name));
+    $this->themeInstaller()->install(array($name));
+    $this->themeInstaller()->uninstall(array($name));
 
     $themes = $this->themeHandler()->listInfo();
     $this->assertFalse(isset($themes[$name]));
@@ -216,11 +216,11 @@ function testUninstallBaseBeforeSubTheme() {
     $name = 'test_basetheme';
     $sub_name = 'test_subtheme';
 
-    $this->themeHandler()->install(array($sub_name));
+    $this->themeInstaller()->install(array($sub_name));
 
     try {
       $message = 'ThemeHandler::install() throws InvalidArgumentException upon uninstalling base theme before sub theme.';
-      $this->themeHandler()->uninstall(array($name));
+      $this->themeInstaller()->uninstall(array($name));
       $this->fail($message);
     }
     catch (\InvalidArgumentException $e) {
@@ -232,7 +232,7 @@ function testUninstallBaseBeforeSubTheme() {
     $this->assertTrue(isset($themes[$sub_name]));
 
     // Verify that uninstalling both at the same time works.
-    $this->themeHandler()->uninstall(array($name, $sub_name));
+    $this->themeInstaller()->uninstall(array($name, $sub_name));
 
     $themes = $this->themeHandler()->listInfo();
     $this->assertFalse(isset($themes[$name]));
@@ -250,7 +250,7 @@ function testUninstallNonExisting() {
 
     try {
       $message = 'ThemeHandler::uninstall() throws InvalidArgumentException upon uninstalling a non-existing theme.';
-      $this->themeHandler()->uninstall(array($name));
+      $this->themeInstaller()->uninstall(array($name));
       $this->fail($message);
     }
     catch (\InvalidArgumentException $e) {
@@ -267,10 +267,10 @@ function testUninstallNonExisting() {
   function testUninstall() {
     $name = 'test_basetheme';
 
-    $this->themeHandler()->install(array($name));
+    $this->themeInstaller()->install(array($name));
     $this->assertTrue($this->config("$name.settings")->get());
 
-    $this->themeHandler()->uninstall(array($name));
+    $this->themeInstaller()->uninstall(array($name));
 
     $this->assertFalse(array_keys($this->themeHandler()->listInfo()));
     $this->assertFalse(array_keys(system_list('theme')));
@@ -278,7 +278,7 @@ function testUninstall() {
     $this->assertFalse($this->config("$name.settings")->get());
 
     // Ensure that the uninstalled theme can be installed again.
-    $this->themeHandler()->install(array($name));
+    $this->themeInstaller()->install(array($name));
     $themes = $this->themeHandler()->listInfo();
     $this->assertTrue(isset($themes[$name]));
     $this->assertEqual($themes[$name]->getName(), $name);
@@ -294,7 +294,7 @@ function testUninstallNotInstalled() {
 
     try {
       $message = 'ThemeHandler::uninstall() throws InvalidArgumentException upon uninstalling a theme that is not installed.';
-      $this->themeHandler()->uninstall(array($name));
+      $this->themeInstaller()->uninstall(array($name));
       $this->fail($message);
     }
     catch (\InvalidArgumentException $e) {
@@ -311,7 +311,7 @@ function testThemeInfoAlter() {
     $name = 'seven';
     $this->container->get('state')->set('module_test.hook_system_info_alter', TRUE);
 
-    $this->themeHandler()->install(array($name));
+    $this->themeInstaller()->install(array($name));
 
     $themes = $this->themeHandler()->listInfo();
     $this->assertFalse(isset($themes[$name]->info['regions']['test_region']));
@@ -362,21 +362,21 @@ protected function themeHandler() {
   }
 
   /**
-   * Returns the system.theme config object.
+   * Returns the theme installer service.
    *
-   * @return \Drupal\Core\Config\Config
+   * @return \Drupal\Core\Extension\ThemeInstallerInterface
    */
-  protected function extensionConfig() {
-    return $this->config('core.extension');
+  protected function themeInstaller() {
+    return $this->container->get('theme_installer');
   }
 
   /**
-   * Returns the active configuration storage.
+   * Returns the system.theme config object.
    *
-   * @return \Drupal\Core\Config\ConfigStorageInterface
+   * @return \Drupal\Core\Config\Config
    */
-  protected function configStorage() {
-    return $this->container->get('config.storage');
+  protected function extensionConfig() {
+    return $this->config('core.extension');
   }
 
   /**
diff --git a/core/modules/system/tests/themes/test_basetheme/config/install/core.date_format.fancy.yml b/core/modules/system/tests/themes/test_basetheme/config/install/core.date_format.fancy.yml
index fc76e730b9c34ee5003fad31b3407914de299960..cdab20542650377a2473cca0d5b91e1333966bb1 100644
--- a/core/modules/system/tests/themes/test_basetheme/config/install/core.date_format.fancy.yml
+++ b/core/modules/system/tests/themes/test_basetheme/config/install/core.date_format.fancy.yml
@@ -1,6 +1,6 @@
 # Themes are not supposed to provide/install this kind of config normally.
 # This exists for testing purposes only.
-# @see \Drupal\system\Tests\Extension\ThemeHandlerTest
+# @see \Drupal\system\Tests\Extension\ThemeInstallerTest
 id: fancy
 label: 'Fancy date'
 status: true
diff --git a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
index be2759fe6abb4afeae716735ab27cc10e7f4698b..3c465b622696eb4af4ff4e0dc6efd1e88d4c7165 100644
--- a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Contains \Drupal\Tests\Core\Extension\ThemeHandlerTest.
+ * Contains \Drupal\Tests\Core\Extension\ThemeInstallerTest.
  */
 
 namespace Drupal\Tests\Core\Extension;
@@ -10,7 +10,6 @@
 use Drupal\Core\Extension\Extension;
 use Drupal\Core\Extension\InfoParser;
 use Drupal\Core\Extension\ThemeHandler;
-use Drupal\Core\Config\ConfigInstaller;
 use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
 use Drupal\Core\State\State;
 use Drupal\Tests\UnitTestCase;
@@ -21,13 +20,6 @@
  */
 class ThemeHandlerTest extends UnitTestCase {
 
-  /**
-   * The mocked route builder.
-   *
-   * @var \Drupal\Core\Routing\RouteBuilderInterface|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $routeBuilder;
-
   /**
    * The mocked info parser.
    *
@@ -56,20 +48,6 @@ class ThemeHandlerTest extends UnitTestCase {
    */
   protected $moduleHandler;
 
-  /**
-   * The mocked config installer.
-   *
-   * @var \Drupal\Core\Config\ConfigInstaller|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $configInstaller;
-
-  /**
-   * The mocked config manager.
-   *
-   * @var \Drupal\Core\Config\ConfigManagerInterface|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $configManager;
-
   /**
    * The extension discovery.
    *
@@ -77,13 +55,6 @@ class ThemeHandlerTest extends UnitTestCase {
    */
   protected $extensionDiscovery;
 
-  /**
-   * The CSS asset collection optimizer service.
-   *
-   * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $cssCollectionOptimizer;
-
   /**
    * The tested theme handler.
    *
@@ -109,17 +80,10 @@ protected function setUp() {
     $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
     $this->state = new State(new KeyValueMemoryFactory());
     $this->infoParser = $this->getMock('Drupal\Core\Extension\InfoParserInterface');
-    $this->configInstaller = $this->getMock('Drupal\Core\Config\ConfigInstallerInterface');
-    $this->configManager = $this->getMock('Drupal\Core\Config\ConfigManagerInterface');
-    $this->routeBuilder = $this->getMock('Drupal\Core\Routing\RouteBuilderInterface');
     $this->extensionDiscovery = $this->getMockBuilder('Drupal\Core\Extension\ExtensionDiscovery')
       ->disableOriginalConstructor()
       ->getMock();
-    $this->cssCollectionOptimizer = $this->getMockBuilder('\Drupal\Core\Asset\CssCollectionOptimizer')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $logger = $this->getMock('Psr\Log\LoggerInterface');
-    $this->themeHandler = new StubThemeHandler($this->root, $this->configFactory, $this->moduleHandler, $this->state, $this->infoParser, $logger, $this->cssCollectionOptimizer, $this->configInstaller, $this->configManager, $this->routeBuilder, $this->extensionDiscovery);
+    $this->themeHandler = new StubThemeHandler($this->root, $this->configFactory, $this->moduleHandler, $this->state, $this->infoParser, $this->extensionDiscovery);
 
     $cache_tags_invalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
     $this->getContainerWithCacheTagsInvalidator($cache_tags_invalidator);