diff --git a/core/config/install/core.extension.yml b/core/config/install/core.extension.yml
index eae39efffbc37899e447adbbf1beefead5789f4b..1514a9e31df55eb534c92e216c08c763f7a656cf 100644
--- a/core/config/install/core.extension.yml
+++ b/core/config/install/core.extension.yml
@@ -1,5 +1,4 @@
 module: {}
-theme:
-  stark: 0
+theme: {}
 disabled:
   theme: {}
diff --git a/core/core.services.yml b/core/core.services.yml
index 46ca46a773c16cda17cd48a05acc3e204ec1ed06..9b9c80bdda6d1dc4809f3aee740afd2ceccef56e 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -203,7 +203,7 @@ services:
     arguments: ['%container.modules%', '@cache.bootstrap']
   theme_handler:
     class: Drupal\Core\Extension\ThemeHandler
-    arguments: ['@config.factory', '@module_handler', '@cache.default', '@info_parser', '@config.installer', '@router.builder']
+    arguments: ['@config.factory', '@module_handler', '@state', '@info_parser', '@config.installer', '@router.builder']
   entity.manager:
     class: Drupal\Core\Entity\EntityManager
     arguments: ['@container.namespaces', '@module_handler', '@cache.discovery', '@language_manager', '@string_translation']
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 9f8088a87cded57f6203565d37725bbcd06ebe65..202f8d5b275926de40150da120e9355152692bbd 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -686,6 +686,8 @@ function install_tasks($install_state) {
       'display_name' => t('Install site'),
       'type' => 'batch',
     ),
+    'install_profile_themes' => array(
+    ),
     'install_import_translations' => array(
       'display_name' => t('Set up translations'),
       'display' => $needs_translations,
@@ -1827,6 +1829,34 @@ function install_profile_modules(&$install_state) {
   return $batch;
 }
 
+/**
+ * Installs themes.
+ *
+ * This does not use a batch, since installing themes is faster than modules and
+ * because an installation profile typically enables 1-3 themes only (default
+ * theme, base theme, admin theme).
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ */
+function install_profile_themes(&$install_state) {
+  $theme_handler = \Drupal::service('theme_handler');
+
+  // ThemeHandler::enable() resets the current list of themes. The theme used in
+  // the installer is not necessarily in the list of themes to install, so
+  // retain the current list.
+  // @see _drupal_maintenance_theme()
+  $current_themes = $theme_handler->listInfo();
+
+  // Install the themes specified by the installation profile.
+  $themes = $install_state['profile_info']['themes'];
+  $theme_handler->enable($themes);
+
+  foreach ($current_themes as $theme) {
+    $theme_handler->addTheme($theme);
+  }
+}
+
 /**
  * Imports languages via a batch process during installation.
  *
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 3789ff33618538debf35abb6426dd76565d3ccf8..ac6e00e26010eb4c482466bfe38d52d586b880eb 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -1077,6 +1077,7 @@ function install_profile_info($profile, $langcode = 'en') {
     // Set defaults for module info.
     $defaults = array(
       'dependencies' => array(),
+      'themes' => array('stark'),
       'description' => '',
       'version' => NULL,
       'hidden' => FALSE,
diff --git a/core/includes/module.inc b/core/includes/module.inc
index 18de4fcdb58511dcb78f5ff40a02cf2f0419e104..cbbe8117452d26a95b50b743637b0031b62d3c27 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -9,26 +9,18 @@
 use Drupal\Core\Extension\ExtensionDiscovery;
 
 /**
- * Builds a list of bootstrap modules and enabled modules and themes.
+ * Builds a list of enabled themes.
  *
  * @param $type
  *   The type of list to return:
- *   - module_enabled: All enabled modules.
- *   - bootstrap: All enabled modules required for bootstrap.
- *   - theme: All themes.
+ *   - theme: All enabled themes.
  *
  * @return
- *   An associative array of modules or themes, keyed by name. For $type
- *   'bootstrap' and 'module_enabled', the array values equal the keys.
+ *   An associative array of themes, keyed by name.
  *   For $type 'theme', the array values are objects representing the
  *   respective database row, with the 'info' property already unserialized.
  *
  * @see list_themes()
- *
- * @todo There are too many layers/levels of caching involved for system_list()
- *   data. Consider to add a \Drupal::config($name, $cache = TRUE) argument to allow
- *   callers like system_list() to force-disable a possible configuration
- *   storage cache or some other way to circumvent it/take it over.
  */
 function system_list($type) {
   $lists = &drupal_static(__FUNCTION__);
@@ -40,32 +32,15 @@ function system_list($type) {
       'theme' => array(),
       'filepaths' => array(),
     );
-    // Build a list of themes.
-    $enabled_themes = \Drupal::config('core.extension')->get('theme') ?: array();
-    // @todo Themes include all themes, including disabled/uninstalled. This
-    //   system.theme.data state will go away entirely as soon as themes have
-    //   a proper installation status.
-    // @see http://drupal.org/node/1067408
-    $theme_data = \Drupal::state()->get('system.theme.data');
-    if (empty($theme_data)) {
-      // @todo: system_list() may be called from _drupal_bootstrap_code(), in
-      // which case system.module is not loaded yet.
-      // Prevent a filesystem scan in drupal_load() and include it directly.
-      // @see http://drupal.org/node/1067408
-      require_once DRUPAL_ROOT . '/core/modules/system/system.module';
-      $theme_data = system_rebuild_theme_data();
-    }
+    // ThemeHandler maintains the 'system.theme.data' state record.
+    $theme_data = \Drupal::state()->get('system.theme.data', array());
     foreach ($theme_data as $name => $theme) {
-      $theme->status = (int) isset($enabled_themes[$name]);
       $lists['theme'][$name] = $theme;
-      // Build a list of filenames so drupal_get_filename can use it.
-      if (isset($enabled_themes[$name])) {
-        $lists['filepaths'][] = array(
-          'type' => 'theme',
-          'name' => $name,
-          'filepath' => $theme->getPathname(),
-        );
-      }
+      $lists['filepaths'][] = array(
+        'type' => 'theme',
+        'name' => $name,
+        'filepath' => $theme->getPathname(),
+      );
     }
     \Drupal::cache('bootstrap')->set('system_list', $lists);
   }
@@ -84,25 +59,17 @@ function system_list($type) {
 function system_list_reset() {
   drupal_static_reset('system_list');
   drupal_static_reset('system_rebuild_module_data');
-  drupal_static_reset('list_themes');
   \Drupal::cache('bootstrap')->delete('system_list');
-  \Drupal::cache()->delete('system_info');
 
   // Clear the library info cache.
   // Libraries may be provided by all extension types, and may be altered by any
   // other extensions (types) due to the nature of
   // \Drupal\Core\Extension\ModuleHandler::alter() and the fact that profiles
   // are recorded and handled as modules.
+  // @todo Trigger an event upon module install/uninstall and theme
+  //   enable/disable, and move this into an event subscriber.
+  // @see https://drupal.org/node/2206347
   Cache::invalidateTags(array('extension' => TRUE));
-
-  // Remove last known theme data state.
-  // This causes system_list() to call system_rebuild_theme_data() on its next
-  // invocation. When enabling a module that implements hook_system_info_alter()
-  // to inject a new (testing) theme or manipulate an existing theme, then that
-  // will cause system_list_reset() to be called, but theme data is not
-  // necessarily rebuilt afterwards.
-  // @todo Obsolete with proper installation status for themes.
-  \Drupal::state()->delete('system.theme.data');
 }
 
 /**
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index d45c2e544c5fa6caef954f4d11404978e45cce77..dc3570bcb07362d09fbed6a18beb9358f4a54027 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -103,7 +103,21 @@ function drupal_theme_initialize() {
   // Determine the active theme for the theme negotiator service. This includes
   // the default theme as well as really specific ones like the ajax base theme.
   $request = \Drupal::request();
-  $theme = \Drupal::service('theme.negotiator')->determineActiveTheme($request) ?: 'stark';
+  $theme = \Drupal::service('theme.negotiator')->determineActiveTheme($request);
+
+  // If no theme could be negotiated, or if the negotiated theme is not within
+  // the list of enabled themes, fall back to the default theme output of core
+  // and modules (similar to Stark, but without a theme extension at all). This
+  // is possible, because _drupal_theme_initialize() always loads the Twig theme
+  // engine.
+  if (!$theme || !isset($themes[$theme])) {
+    $theme = 'core';
+    $theme_key = $theme;
+    // /core/core.info.yml does not actually exist, but is required because
+    // Extension expects a pathname.
+    _drupal_theme_initialize(new Extension('theme', 'core/core.info.yml'));
+    return;
+  }
 
   // Store the identifier for retrieving theme settings with.
   $theme_key = $theme;
@@ -401,6 +415,8 @@ function _theme($hook, $variables = array()) {
   if (!$module_handler->isLoaded() && !defined('MAINTENANCE_MODE')) {
     throw new Exception(t('_theme() may not be called until all modules are loaded.'));
   }
+  // Ensure the theme is initialized.
+  drupal_theme_initialize();
 
   /** @var \Drupal\Core\Utility\ThemeRegistry $theme_registry */
   $theme_registry = \Drupal::service('theme.registry')->getRuntime();
@@ -851,8 +867,8 @@ function theme_get_setting($setting_name, $theme = NULL) {
 
     // Get the values for the theme-specific settings from the .info.yml files
     // of the theme and all its base themes.
-    if ($theme) {
-      $themes = list_themes();
+    $themes = list_themes();
+    if ($theme && isset($themes[$theme])) {
       $theme_object = $themes[$theme];
 
       // Create a list which includes the current theme and all its base themes.
@@ -874,7 +890,7 @@ function theme_get_setting($setting_name, $theme = NULL) {
     // Get the global settings from configuration.
     $cache[$theme]->merge(\Drupal::config('system.theme.global')->get());
 
-    if ($theme) {
+    if ($theme && isset($themes[$theme])) {
       // Retrieve configured theme-specific settings, if any.
       try {
         if ($theme_settings = \Drupal::config($theme . '.settings')->get()) {
@@ -975,13 +991,16 @@ function theme_settings_convert_to_config(array $theme_settings, Config $config)
  * @param $theme_list
  *   An array of theme names.
  *
+ * @return bool
+ *   Whether any of the given themes have been enabled.
+ *
  * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  *   Use \Drupal::service('theme_handler')->enable().
  *
  * @see \Drupal\Core\Extension\ThemeHandler::enable().
  */
 function theme_enable($theme_list) {
-  \Drupal::service('theme_handler')->enable($theme_list);
+  return \Drupal::service('theme_handler')->enable($theme_list);
 }
 
 /**
@@ -990,13 +1009,16 @@ function theme_enable($theme_list) {
  * @param $theme_list
  *   An array of theme names.
  *
+ * @return bool
+ *   Whether any of the given themes have been disabled.
+ *
  * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
  *   Use \Drupal::service('theme_handler')->disable().
  *
  * @see \Drupal\Core\Extension\ThemeHandler::disable().
  */
 function theme_disable($theme_list) {
-  \Drupal::service('theme_handler')->disable($theme_list);
+  return \Drupal::service('theme_handler')->disable($theme_list);
 }
 
 /**
diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc
index 1cd436c4837dfa8a3dc2194a7c0e8ff338f71046..a79adca5ffee9c5e37e8f80c4d06ec2b8439f186 100644
--- a/core/includes/theme.maintenance.inc
+++ b/core/includes/theme.maintenance.inc
@@ -66,7 +66,7 @@ function _drupal_maintenance_theme() {
   }
 
   // Ensure that system.module is loaded.
-  if (!function_exists('_system_rebuild_theme_data')) {
+  if (!function_exists('system_rebuild_theme_data')) {
     $module_handler = \Drupal::moduleHandler();
     $module_handler->addModule('system', 'core/modules/system');
     $module_handler->load('system');
@@ -74,6 +74,14 @@ function _drupal_maintenance_theme() {
 
   $themes = list_themes();
 
+  // If no themes are installed yet, or if the requested custom theme is not
+  // installed, retrieve all available themes.
+  if (empty($themes) || !isset($themes[$custom_theme])) {
+    $theme_handler = \Drupal::service('theme_handler');
+    $themes = $theme_handler->rebuildThemeData();
+    $theme_handler->addTheme($themes[$custom_theme]);
+  }
+
   // list_themes() triggers a \Drupal\Core\Extension\ModuleHandler::alter() in
   // maintenance mode, but we can't let themes alter the .info.yml data until
   // we know a theme's base themes. So don't set global $theme until after
diff --git a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
index 60dfb53d410cc077d6fd590b1cab474a9aa9c062..32ad9b768252c24cec3ed75a43d6f9aa4705e4e7 100644
--- a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
+++ b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
@@ -41,7 +41,7 @@ public function register(ContainerBuilder $container) {
       $container->register('theme_handler', 'Drupal\Core\Extension\ThemeHandler')
         ->addArgument(new Reference('config.factory'))
         ->addArgument(new Reference('module_handler'))
-        ->addArgument(new Reference('cache.default'))
+        ->addArgument(new Reference('state'))
         ->addArgument(new Reference('info_parser'));
     }
   }
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 537a844cb55acfd995e07d424205a47e0424b495..08a3ec5e795cdd7614ccdfac0e23f3aab2464ae5 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -758,8 +758,6 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
 
         // Refresh the schema to include it.
         drupal_get_schema(NULL, TRUE);
-        // Update the theme registry to include it.
-        drupal_theme_rebuild();
 
         // Allow modules to react prior to the installation of a module.
         $this->invokeAll('module_preinstall', array($module));
@@ -804,8 +802,18 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
         // Record the fact that it was installed.
         $modules_installed[] = $module;
 
+        // Update the theme registry to include it.
+        drupal_theme_rebuild();
+
+        // Modules can alter theme info, so refresh theme data.
+        // @todo ThemeHandler cannot be injected into ModuleHandler, since that
+        //   causes a circular service dependency.
+        // @see https://drupal.org/node/2208429
+        \Drupal::service('theme_handler')->refreshInfo();
+
         // Allow the module to perform install tasks.
         $this->invoke($module, 'install');
+
         // Record the fact that it was installed.
         watchdog('system', '%module module installed.', array('%module' => $module), WATCHDOG_INFO);
       }
@@ -912,6 +920,12 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
       // Update the theme registry to remove the newly uninstalled module.
       drupal_theme_rebuild();
 
+      // Modules can alter theme info, so refresh theme data.
+      // @todo ThemeHandler cannot be injected into ModuleHandler, since that
+      //   causes a circular service dependency.
+      // @see https://drupal.org/node/2208429
+      \Drupal::service('theme_handler')->refreshInfo();
+
       watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO);
 
       $schema_store->delete($module);
diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php
index b43d3e47bea76b54c1cd49caaf10b1987e90f347..b3b368b49efeb6b0e567f409dbda3a10893cfbbe 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandler.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php
@@ -9,9 +9,9 @@
 
 use Drupal\Component\Utility\String;
 use Drupal\Core\Cache\Cache;
-use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Config\ConfigInstallerInterface;
+use Drupal\Core\State\StateInterface;
 use Drupal\Core\Routing\RouteBuilder;
 
 /**
@@ -41,7 +41,7 @@ class ThemeHandler implements ThemeHandlerInterface {
    *
    * @var array
    */
-  protected $list = array();
+  protected $list;
 
   /**
    * The config factory to get the enabled themes.
@@ -58,11 +58,11 @@ class ThemeHandler implements ThemeHandlerInterface {
   protected $moduleHandler;
 
   /**
-   * The cache backend to clear the local tasks cache.
+   * The state backend.
    *
-   * @var \Drupal\Core\Cache\CacheBackendInterface
+   * @var \Drupal\Core\State\StateInterface
    */
-  protected $cacheBackend;
+  protected $state;
 
   /**
    *  The config installer to install configuration.
@@ -99,8 +99,8 @@ class ThemeHandler implements ThemeHandlerInterface {
    *   The config factory to get the enabled themes.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to fire themes_enabled/themes_disabled hooks.
-   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
-   *   The cache backend to clear the local tasks cache.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state store.
    * @param \Drupal\Core\Extension\InfoParserInterface $info_parser
    *   The info parser to parse the theme.info.yml files.
    * @param \Drupal\Core\Config\ConfigInstallerInterface $config_installer
@@ -112,10 +112,10 @@ class ThemeHandler implements ThemeHandlerInterface {
    * @param \Drupal\Core\Extension\ExtensionDiscovery $extension_discovery
    *   (optional) A extension discovery instance (for unit tests).
    */
-  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, InfoParserInterface $info_parser, ConfigInstallerInterface $config_installer = NULL, RouteBuilder $route_builder = NULL, ExtensionDiscovery $extension_discovery = NULL) {
+  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, InfoParserInterface $info_parser, ConfigInstallerInterface $config_installer = NULL, RouteBuilder $route_builder = NULL, ExtensionDiscovery $extension_discovery = NULL) {
     $this->configFactory = $config_factory;
     $this->moduleHandler = $module_handler;
-    $this->cacheBackend = $cache_backend;
+    $this->state = $state;
     $this->infoParser = $info_parser;
     $this->configInstaller = $config_installer;
     $this->routeBuilder = $route_builder;
@@ -125,10 +125,90 @@ public function __construct(ConfigFactoryInterface $config_factory, ModuleHandle
   /**
    * {@inheritdoc}
    */
-  public function enable(array $theme_list) {
-    $this->clearCssCache();
+  public function getDefault() {
+    return $this->configFactory->get('system.theme')->get('default');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setDefault($name) {
+    if (!isset($this->list)) {
+      $this->listInfo();
+    }
+    if (!isset($this->list[$name])) {
+      throw new \InvalidArgumentException("$name theme is not enabled.");
+    }
+    $this->configFactory->get('system.theme')
+      ->set('default', $name)
+      ->save();
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function enable(array $theme_list, $enable_dependencies = TRUE) {
     $extension_config = $this->configFactory->get('core.extension');
+
+    $theme_data = $this->rebuildThemeData();
+
+    if ($enable_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(String::format('Unknown themes: !themes.', array(
+          '!themes' => implode(', ', $missing),
+        )));
+      }
+
+      // Only process themes that are not enabled currently.
+      $installed_themes = $extension_config->get('theme') ?: array();
+      if (!$theme_list = array_diff_key($theme_list, $installed_themes)) {
+        // Nothing to do. All themes already enabled.
+        return TRUE;
+      }
+      $installed_themes += $extension_config->get('disabled.theme') ?: array();
+
+      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();
+      $installed_themes += $extension_config->get('disabled.theme') ?: array();
+    }
+
+    $themes_enabled = array();
     foreach ($theme_list as $key) {
+      // Only process themes that are not already enabled.
+      $enabled = $extension_config->get("theme.$key") !== NULL;
+      if ($enabled) {
+        continue;
+      }
+
       // Throw an exception if the theme name is too long.
       if (strlen($key) > DRUPAL_EXTENSION_NAME_MAX_LENGTH) {
         throw new ExtensionNameLengthException(String::format('Theme name %name is over the maximum allowed length of @max characters.', array(
@@ -143,53 +223,107 @@ public function enable(array $theme_list) {
         ->clear("disabled.theme.$key")
         ->save();
 
-      // Refresh the theme list as installation of default configuration needs
-      // an updated list to work.
-      $this->reset();
-
-      // The default config installation storage only knows about the currently
-      // enabled list of themes, so it has to be reset in order to pick up the
-      // default config of the newly installed theme. However, do not reset the
-      // source storage when synchronizing configuration, since that would
-      // needlessly trigger a reload of the whole configuration to be imported.
-      if (!$this->configInstaller->isSyncing()) {
-        $this->configInstaller->resetSourceStorage();
+      // 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])) {
+        // The default config installation storage only knows about the currently
+        // enabled list of themes, so it has to be reset in order to pick up the
+        // default config of the newly installed theme. However, do not reset the
+        // source storage when synchronizing configuration, since that would
+        // needlessly trigger a reload of the whole configuration to be imported.
+        if (!$this->configInstaller->isSyncing()) {
+          $this->configInstaller->resetSourceStorage();
+        }
+
+        // Install default configuration of the theme.
+        $this->configInstaller->installDefaultConfig('theme', $key);
       }
-      // Install default configuration of the theme.
-      $this->configInstaller->installDefaultConfig('theme', $key);
+
+      $themes_enabled[] = $key;
+
+      // Record the fact that it was enabled.
+      watchdog('system', '%theme theme enabled.', array('%theme' => $key), WATCHDOG_INFO);
     }
 
+    $this->clearCssCache();
     $this->resetSystem();
 
     // Invoke hook_themes_enabled() after the themes have been enabled.
-    $this->moduleHandler->invokeAll('themes_enabled', array($theme_list));
+    $this->moduleHandler->invokeAll('themes_enabled', array($themes_enabled));
+
+    return !empty($themes_enabled);
   }
 
   /**
    * {@inheritdoc}
    */
   public function disable(array $theme_list) {
-    // Don't disable the default or admin themes.
     $theme_config = $this->configFactory->get('system.theme');
-    $default_theme = $theme_config->get('default');
-    $admin_theme = $theme_config->get('admin');
-    $theme_list = array_diff($theme_list, array($default_theme, $admin_theme));
-    if (empty($theme_list)) {
-      return;
+
+    foreach ($theme_list as $key) {
+      if (!isset($this->list[$key])) {
+        throw new \InvalidArgumentException("Unknown theme: $key.");
+      }
+      if ($key === $theme_config->get('default')) {
+        throw new \InvalidArgumentException("The current default theme $key cannot be disabled.");
+      }
+      if ($key === $theme_config->get('admin')) {
+        throw new \InvalidArgumentException("The current admin theme $key cannot be disabled.");
+      }
+      // Base themes cannot be disabled if sub themes are enabled, and if they
+      // are not disabled at the same time.
+      if (!empty($this->list[$key]->sub_themes)) {
+        foreach ($this->list[$key]->sub_themes as $sub_key => $sub_label) {
+          if (isset($this->list[$sub_key]) && !in_array($sub_key, $theme_list, TRUE)) {
+            throw new \InvalidArgumentException("The base theme $key cannot be disabled, because theme $sub_key depends on it.");
+          }
+        }
+      }
     }
 
     $this->clearCssCache();
 
     $extension_config = $this->configFactory->get('core.extension');
+    $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")
         ->set("disabled.theme.$key", 0);
+
+      // 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();
     }
     $extension_config->save();
+    $this->state->set('system.theme.data', $current_theme_data);
 
-    $this->reset();
     $this->resetSystem();
 
     // Invoke hook_themes_disabled after the themes have been disabled.
@@ -200,52 +334,63 @@ public function disable(array $theme_list) {
    * {@inheritdoc}
    */
   public function listInfo() {
-    if (empty($this->list)) {
+    if (!isset($this->list)) {
       $this->list = array();
-      try {
-        $themes = $this->systemThemeList();
+      $themes = $this->systemThemeList();
+      foreach ($themes as $theme) {
+        $this->addTheme($theme);
       }
-      catch (\Exception $e) {
-        // If the database is not available, rebuild the theme data.
-        $themes = $this->rebuildThemeData();
+    }
+    return $this->list;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addTheme(Extension $theme) {
+    // @todo Remove this 100% unnecessary duplication of properties.
+    foreach ($theme->info['stylesheets'] as $media => $stylesheets) {
+      foreach ($stylesheets as $stylesheet => $path) {
+        $theme->stylesheets[$media][$stylesheet] = $path;
       }
+    }
+    foreach ($theme->info['libraries'] as $library => $name) {
+      $theme->libraries[$library] = $name;
+    }
+    if (isset($theme->info['engine'])) {
+      $theme->engine = $theme->info['engine'];
+    }
+    if (isset($theme->info['base theme'])) {
+      $theme->base_theme = $theme->info['base theme'];
+    }
+    $this->list[$theme->getName()] = $theme;
+  }
 
-      foreach ($themes as $theme) {
-        foreach ($theme->info['stylesheets'] as $media => $stylesheets) {
-          foreach ($stylesheets as $stylesheet => $path) {
-            $theme->stylesheets[$media][$stylesheet] = $path;
-          }
-        }
-        foreach ($theme->info['libraries'] as $library => $name) {
-          $theme->libraries[$library] = $name;
-        }
-        if (isset($theme->info['engine'])) {
-          $theme->engine = $theme->info['engine'];
-        }
-        if (isset($theme->info['base theme'])) {
-          $theme->base_theme = $theme->info['base theme'];
-        }
-        // Status is normally retrieved from the database. Add zero values when
-        // read from the installation directory to prevent notices.
-        if (!isset($theme->status)) {
-          $theme->status = 0;
-        }
-        $this->list[$theme->getName()] = $theme;
+  /**
+   * {@inheritdoc}
+   */
+  public function refreshInfo() {
+    $this->reset();
+    $extension_config = $this->configFactory->get('core.extension');
+    $enabled = $extension_config->get('theme') ?: array();
+
+    // @todo Avoid re-scanning all themes by retaining the original (unaltered)
+    //   theme info somewhere.
+    $list = $this->rebuildThemeData();
+    foreach ($list as $name => $theme) {
+      if (isset($enabled[$name])) {
+        $this->list[$name] = $theme;
       }
     }
-    return $this->list;
+    $this->state->set('system.theme.data', $this->list);
   }
 
   /**
    * {@inheritdoc}
    */
   public function reset() {
-    // listInfo() calls system_info() which has a lot of side effects that have
-    // to be triggered like the classloading of theme classes.
-    $this->list = array();
     $this->systemListReset();
-    $this->listInfo();
-    $this->list = array();
+    $this->list = NULL;
   }
 
   /**
@@ -255,6 +400,8 @@ public function rebuildThemeData() {
     $listing = $this->getExtensionDiscovery();
     $themes = $listing->scan('theme');
     $engines = $listing->scan('theme_engine');
+    $extension_config = $this->configFactory->get('core.extension');
+    $enabled = $extension_config->get('theme') ?: array();
 
     // Set defaults for theme info.
     $defaults = array(
@@ -279,8 +426,12 @@ public function rebuildThemeData() {
     );
 
     $sub_themes = array();
+    $files = array();
     // Read info files for each theme.
     foreach ($themes as $key => $theme) {
+      // @todo Remove all code that relies on the $status property.
+      $theme->status = (int) isset($enabled[$key]);
+
       $theme->info = $this->infoParser->parse($theme->getPathname()) + $defaults;
 
       // Add the info file modification time, so it becomes available for
@@ -295,6 +446,8 @@ public function rebuildThemeData() {
 
       if (!empty($theme->info['base theme'])) {
         $sub_themes[] = $key;
+        // Add the base theme as a proper dependency.
+        $themes[$key]->info['dependencies'][] = $themes[$key]->info['base theme'];
       }
 
       // Defaults to 'twig' (see $defaults above).
@@ -310,7 +463,17 @@ public function rebuildThemeData() {
       if (!empty($theme->info['screenshot'])) {
         $theme->info['screenshot'] = $path . '/' . $theme->info['screenshot'];
       }
+
+      $files[$key] = $theme->getPathname();
     }
+    // Build dependencies.
+    // @todo Move into a generic ExtensionHandler base class.
+    // @see https://drupal.org/node/2208429
+    $themes = $this->moduleHandler->buildModuleDependencies($themes);
+
+    // Store filenames to allow system_list() and drupal_get_filename() to
+    // retrieve them without having to scan the filesystem.
+    $this->state->set('system.theme.files', $files);
 
     // After establishing the full list of available themes, fill in data for
     // sub-themes.
diff --git a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
index 2ba52cd3796cef5983e355d1461cc030c7bb926e..c6194ea55cd213a07facc1cf796f4c33cac787a1 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
@@ -17,28 +17,35 @@ interface ThemeHandlerInterface {
    *
    * @param array $theme_list
    *   An array of theme names.
+   * @param bool $enable_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 enabled.
    *
    * @throws \Drupal\Core\Extension\ExtensionNameLengthException
    *   Thrown when the theme name is to long
    */
-  public function enable(array $theme_list);
+  public function enable(array $theme_list, $enable_dependencies = TRUE);
 
   /**
    * Disables a given list of themes.
    *
    * @param array $theme_list
    *   An array of theme names.
+   *
+   * @return bool
+   *   Whether any of the given themes have been disabled.
    */
   public function disable(array $theme_list);
 
   /**
-   * Returns a list of all currently available themes.
-   *
-   * Retrieved from the database, if available and the site is not in
-   * maintenance mode; otherwise compiled freshly from the filesystem.
+   * Returns a list of currently enabled themes.
    *
    * @return \Drupal\Core\Extension\Extension[]
-   *   An associative array of the currently available themes. The keys are the
+   *   An associative array of the currently enabled themes. The keys are the
    *   themes' machine names and the values are objects having the following
    *   properties:
    *   - filename: The filepath and name of the .info.yml file.
@@ -75,6 +82,14 @@ public function disable(array $theme_list);
    */
   public function listInfo();
 
+  /**
+   * Refreshes the theme info data of currently enabled themes.
+   *
+   * Modules can alter theme info, so this is typically called after a module
+   * has been installed or uninstalled.
+   */
+  public function refreshInfo();
+
   /**
    * Resets the internal state of the theme handler.
    */
diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php
index e84b33b9b4d46d9c9e6d05561c4193a102c302dc..402c9f79d62d6fe5c13e4e20ba2c9cab1ca7c715 100644
--- a/core/lib/Drupal/Core/Theme/Registry.php
+++ b/core/lib/Drupal/Core/Theme/Registry.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\DestructableInterface;
+use Drupal\Core\Extension\Extension;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Lock\LockBackendInterface;
 use Drupal\Core\Utility\ThemeRegistry;
@@ -145,23 +146,17 @@ public function __construct(CacheBackendInterface $cache, LockBackendInterface $
   protected function init($theme_name = NULL) {
     // Unless instantiated for a specific theme, use globals.
     if (!isset($theme_name)) {
-      // #1: The theme registry might get instantiated before the theme was
-      // initialized. Cope with that.
-      if (!isset($GLOBALS['theme_info']) || !isset($GLOBALS['theme'])) {
-        unset($this->runtimeRegistry);
-        unset($this->registry);
-        drupal_theme_initialize();
+      if (isset($GLOBALS['theme']) && isset($GLOBALS['theme_info'])) {
+        $this->theme = $GLOBALS['theme_info'];
+        $this->baseThemes = $GLOBALS['base_theme_info'];
+        $this->engine = $GLOBALS['theme_engine'];
       }
-      // #2: The testing framework only cares for the global $theme variable at
-      // this point. Cope with that.
-      if ($GLOBALS['theme'] != $GLOBALS['theme_info']->getName()) {
-        unset($this->runtimeRegistry);
-        unset($this->registry);
-        $this->initializeTheme();
+      else {
+        // @see drupal_theme_initialize()
+        $this->theme = new Extension('theme', 'core/core.info.yml');
+        $this->baseThemes = array();
+        $this->engine = 'twig';
       }
-      $this->theme = $GLOBALS['theme_info'];
-      $this->baseThemes = $GLOBALS['base_theme_info'];
-      $this->engine = $GLOBALS['theme_engine'];
     }
     // Instead of the global theme, a specific theme was requested.
     else {
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
index 92b7656ad3cc18935ff0fbb43d3d2449fd1a6bbb..b95a666c4a2e82d4d2c6328486528a87ddaa1dc2 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
@@ -211,7 +211,7 @@ function testImport() {
     $this->assertTrue(empty($installed), 'No modules installed during import');
 
     $theme_info = \Drupal::service('theme_handler')->listInfo();
-    $this->assertTrue(isset($theme_info['bartik']) && !$theme_info['bartik']->status, 'Bartik theme disabled during import.');
+    $this->assertFalse(isset($theme_info['bartik']), 'Bartik theme disabled during import.');
 
     // Verify that the action.settings configuration object was only deleted
     // once during the import process.
diff --git a/core/modules/config_translation/config_translation.module b/core/modules/config_translation/config_translation.module
index 74047f71b4f753e341ac09d99b210aa24defcdfb..e30f09a59b66f32015bda5733f26c976277379f3 100644
--- a/core/modules/config_translation/config_translation.module
+++ b/core/modules/config_translation/config_translation.module
@@ -55,6 +55,28 @@ function config_translation_theme() {
   );
 }
 
+/**
+ * Implements hook_themes_enabled().
+ */
+function config_translation_themes_enabled() {
+  // Themes can provide *.config_translation.yml declarations.
+  // @todo Make ThemeHandler trigger an event instead and make
+  //   ConfigMapperManager plugin manager subscribe to it.
+  // @see https://drupal.org/node/2206347
+  \Drupal::service('plugin.manager.config_translation.mapper')->clearCachedDefinitions();
+}
+
+/**
+ * Implements hook_themes_disabled().
+ */
+function config_translation_themes_disabled() {
+  // Themes can provide *.config_translation.yml declarations.
+  // @todo Make ThemeHandler trigger an event instead and make
+  //   ConfigMapperManager plugin manager subscribe to it.
+  // @see https://drupal.org/node/2206347
+  \Drupal::service('plugin.manager.config_translation.mapper')->clearCachedDefinitions();
+}
+
 /**
  * Implements hook_entity_type_alter().
  */
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/ConfigMapperManager.php b/core/modules/config_translation/lib/Drupal/config_translation/ConfigMapperManager.php
index bdfbe5555d193857155380493a7004c41b2ab785..e90c5bff3a65975d6f835169cb9e9f7ca5304487 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/ConfigMapperManager.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/ConfigMapperManager.php
@@ -67,6 +67,14 @@ public function __construct(CacheBackendInterface $cache_backend, LanguageManage
     $this->typedConfigManager = $typed_config_manager;
 
     // Look at all themes and modules.
+    // @todo If the list of enabled modules and themes is changed, new
+    //   definitions are not picked up immediately and obsolete definitions are
+    //   not removed, because the list of search directories is only compiled
+    //   once in this constructor. The current code only works due to
+    //   coincidence: The request that enables e.g. a new theme does not
+    //   instantiate this plugin manager at the beginning of the request; when
+    //   routes are being rebuilt at the end of the request, this service only
+    //   happens to get instantiated with the updated list of enabled themes.
     $directories = array();
     foreach ($module_handler->getModuleList() as $name => $module) {
       $directories[$name] = $module->getPath();
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php
index 551b7f2e345d9ea39a8e5a6fd188a3202c5dc22e..46ef47f1259d8aa1090320d929a341ea2dc57a1c 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php
@@ -654,25 +654,6 @@ public function testAlterInfo() {
     $this->assertNoText(t('Password recovery'));
   }
 
-  /**
-   * Tests that theme provided *.config_translation.yml files are found.
-   */
-  public function testThemeDiscovery() {
-    // Enable the test theme and rebuild routes.
-    $theme = 'config_translation_test_theme';
-    theme_enable(array($theme));
-    // Enabling a theme will cause the kernel terminate event to rebuild the
-    // router. Simulate that here.
-    \Drupal::service('router.builder')->rebuildIfNeeded();
-
-    $this->drupalLogin($this->admin_user);
-
-    $translation_base_url = 'admin/config/development/performance/translate';
-    $this->drupalGet($translation_base_url);
-    $this->assertResponse(200);
-    $this->assertLinkByHref("$translation_base_url/fr/add");
-  }
-
   /**
    * Gets translation from locale storage.
    *
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiThemeTest.php b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiThemeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b51cee3edeae7a746a0c8db89f2b2e604f88f9dc
--- /dev/null
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiThemeTest.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config_translation\Tests\ConfigTranslationUiThemeTest.
+ */
+
+namespace Drupal\config_translation\Tests;
+
+use Drupal\Core\Language\Language;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Functional tests for the Language list configuration forms.
+ */
+class ConfigTranslationUiThemeTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('config_translation', 'config_translation_test');
+
+  /**
+   * Languages to enable.
+   *
+   * @var array
+   */
+  protected $langcodes = array('fr', 'ta');
+
+  /**
+   * Administrator user for tests.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $admin_user;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Theme Configuration Translation',
+      'description' => 'Verifies theme configuration translation settings.',
+      'group' => 'Configuration Translation',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+
+    $admin_permissions = array(
+      'administer themes',
+      'administer languages',
+      'administer site configuration',
+      'translate configuration',
+    );
+    // Create and login user.
+    $this->admin_user = $this->drupalCreateUser($admin_permissions);
+
+    // Add languages.
+    foreach ($this->langcodes as $langcode) {
+      $language = new Language(array('id' => $langcode));
+      language_save($language);
+    }
+  }
+
+  /**
+   * Tests that theme provided *.config_translation.yml files are found.
+   */
+  public function testThemeDiscovery() {
+    // Enable the test theme and rebuild routes.
+    $theme = 'config_translation_test_theme';
+
+    $this->drupalLogin($this->admin_user);
+
+    $this->drupalGet('admin/appearance');
+    $elements = $this->xpath('//a[normalize-space()=:label and contains(@href, :theme)]', array(
+      ':label' => 'Enable and set as default',
+      ':theme' => $theme,
+    ));
+    $this->drupalGet($GLOBALS['base_root'] . $elements[0]['href'], array('external' => TRUE));
+
+    $translation_base_url = 'admin/config/development/performance/translate';
+    $this->drupalGet($translation_base_url);
+    $this->assertResponse(200);
+    $this->assertLinkByHref("$translation_base_url/fr/add");
+  }
+
+}
diff --git a/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.module b/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.module
index c71166f0087158770ec077ac524a0be543043356..d38be9733923a8b9c0e6bcbaf043a7a1530be976 100644
--- a/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.module
+++ b/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.module
@@ -5,6 +5,18 @@
  * Configuration Translation Test module.
  */
 
+use Drupal\Core\Extension\Extension;
+
+/**
+ * Implements hook_system_info_alter().
+ */
+function config_translation_test_system_info_alter(array &$info, Extension $file, $type) {
+  // @see \Drupal\config_translation\Tests\ConfigTranslationUiThemeTest
+  if ($file->getType() == 'theme' && $file->getName() == 'config_translation_test_theme') {
+    $info['hidden'] = FALSE;
+  }
+}
+
 /**
  * Implements hook_entity_type_alter().
  */
diff --git a/core/modules/system/lib/Drupal/system/Controller/SystemController.php b/core/modules/system/lib/Drupal/system/Controller/SystemController.php
index eda620d8053d11e87a29059787205616066e1d10..923fe6a3f4b5aef76be84fabf093ccb1d53daa5b 100644
--- a/core/modules/system/lib/Drupal/system/Controller/SystemController.php
+++ b/core/modules/system/lib/Drupal/system/Controller/SystemController.php
@@ -183,16 +183,19 @@ public function systemAdminMenuBlockPage() {
    *
    * @return string
    *   An HTML string of the theme listing page.
+   *
+   * @todo Move into ThemeController.
    */
   public function themesPage() {
     $config = $this->config('system.theme');
-    // Get current list of themes.
-    $themes = $this->themeHandler->listInfo();
+    // Get all available themes.
+    $themes = $this->themeHandler->rebuildThemeData();
     uasort($themes, 'system_sort_modules_by_info_name');
 
     $theme_default = $config->get('default');
-    $theme_groups  = array();
+    $theme_groups  = array('enabled' => array(), 'disabled' => array());
     $admin_theme = $config->get('admin');
+    $admin_theme_options = array();
 
     foreach ($themes as &$theme) {
       if (!empty($theme->info['hidden'])) {
diff --git a/core/modules/system/lib/Drupal/system/Controller/ThemeController.php b/core/modules/system/lib/Drupal/system/Controller/ThemeController.php
index 86f6a4d2bece293b5c1924db501152fa345daf93..51d91f978bc1699bd1c90800dfcde3d51056be5d 100644
--- a/core/modules/system/lib/Drupal/system/Controller/ThemeController.php
+++ b/core/modules/system/lib/Drupal/system/Controller/ThemeController.php
@@ -115,12 +115,8 @@ public function enable(Request $request) {
     $theme = $request->get('theme');
 
     if (isset($theme)) {
-      // Get current list of themes.
-      $themes = $this->themeHandler->listInfo();
-
-      // Check if the specified theme is one recognized by the system.
-      if (!empty($themes[$theme])) {
-        $this->themeHandler->enable(array($theme));
+      if ($this->themeHandler->enable(array($theme))) {
+        $themes = $this->themeHandler->listInfo();
         drupal_set_message($this->t('The %theme theme has been enabled.', array('%theme' => $themes[$theme]->info['name'])));
       }
       else {
@@ -154,11 +150,9 @@ public function setDefaultTheme(Request $request) {
       $themes = $this->themeHandler->listInfo();
 
       // Check if the specified theme is one recognized by the system.
-      if (!empty($themes[$theme])) {
-        // Enable the theme if it is currently disabled.
-        if (empty($themes[$theme]->status)) {
-          $this->themeHandler->enable(array($theme));
-        }
+      // Or try to enable the theme.
+      if (isset($themes[$theme]) || $this->themeHandler->enable(array($theme))) {
+        $themes = $this->themeHandler->listInfo();
 
         // Set the default theme.
         $config->set('default', $theme)->save();
diff --git a/core/modules/system/lib/Drupal/system/Tests/Batch/PageTest.php b/core/modules/system/lib/Drupal/system/Tests/Batch/PageTest.php
index f268e0279f7fb4ceab72f609b9737ef64c1d78f0..9c15510dcc271b6ca61bca033d81dc74071ee076 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Batch/PageTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Batch/PageTest.php
@@ -35,11 +35,12 @@ public static function getInfo() {
   function testBatchProgressPageTheme() {
     // Make sure that the page which starts the batch (an administrative page)
     // is using a different theme than would normally be used by the batch API.
-    \Drupal::config('system.theme')
+    $this->container->get('theme_handler')->enable(array('seven', 'bartik'));
+    $this->container->get('config.factory')->get('system.theme')
       ->set('default', 'bartik')
+      ->set('admin', 'seven')
       ->save();
-    theme_enable(array('seven'));
-    \Drupal::config('system.theme')->set('admin', 'seven')->save();
+
     // Log in as an administrator who can see the administrative theme.
     $admin_user = $this->drupalCreateUser(array('view the administration theme'));
     $this->drupalLogin($admin_user);
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/RenderElementTypesTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/RenderElementTypesTest.php
index ef76a078e5d22777b8555d137f77b0343f08f470..15a1904538a024d2cd0402e675c004e1a5fc0ee2 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/RenderElementTypesTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/RenderElementTypesTest.php
@@ -33,7 +33,6 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
     $this->installConfig(array('system'));
-    $this->container->get('theme_handler')->enable(array('stark'));
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Extension/ThemeHandlerTest.php b/core/modules/system/lib/Drupal/system/Tests/Extension/ThemeHandlerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0f5ecdc6e0c74b79425a691eda47802ef9756ca8
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Extension/ThemeHandlerTest.php
@@ -0,0 +1,426 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Extension\ThemeHandlerTest.
+ */
+
+namespace Drupal\system\Tests\Extension;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Extension\ExtensionNameLengthException;
+use Drupal\simpletest\DrupalUnitTestBase;
+
+/**
+ * Tests installing/enabling, disabling, and uninstalling of themes.
+ */
+class ThemeHandlerTest extends DrupalUnitTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('system');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Theme handler',
+      'description' => 'Tests installing/enabling, disabling, and uninstalling of themes.',
+      'group' => 'Extension',
+    );
+  }
+
+  public function containerBuild(ContainerBuilder $container) {
+    parent::containerBuild($container);
+    // Some test methods involve ModuleHandler operations, which attempt to
+    // rebuild and dump routes.
+    $container
+      ->register('router.dumper', 'Drupal\Core\Routing\NullMatcherDumper');
+  }
+
+  function setUp() {
+    parent::setUp();
+    $this->installConfig(array('system'));
+  }
+
+  /**
+   * Verifies that no themes are installed/enabled/disabled by default.
+   */
+  function testEmpty() {
+    $this->assertFalse($this->extensionConfig()->get('theme'));
+    $this->assertFalse($this->extensionConfig()->get('disabled.theme'));
+
+    $this->assertFalse(array_keys($this->themeHandler()->listInfo()));
+    $this->assertFalse(array_keys(system_list('theme')));
+
+    // Rebuilding available themes should always yield results though.
+    $this->assertTrue($this->themeHandler()->rebuildThemeData()['stark'], 'ThemeHandler::rebuildThemeData() yields all available themes.');
+
+    // theme_get_setting() should return global default theme settings.
+    $this->assertIdentical(theme_get_setting('features.favicon'), TRUE);
+  }
+
+  /**
+   * Tests enabling a theme.
+   */
+  function testEnable() {
+    $name = 'test_basetheme';
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertFalse(isset($themes[$name]));
+
+    $this->themeHandler()->enable(array($name));
+
+    $this->assertIdentical($this->extensionConfig()->get("theme.$name"), 0);
+    $this->assertNull($this->extensionConfig()->get("disabled.theme.$name"));
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertTrue(isset($themes[$name]));
+    $this->assertEqual($themes[$name]->getName(), $name);
+
+    $this->assertEqual(array_keys(system_list('theme')), array_keys($themes));
+
+    // Verify that test_basetheme.settings is active.
+    $this->assertIdentical(theme_get_setting('features.favicon', $name), FALSE);
+    $this->assertEqual(theme_get_setting('base', $name), 'only');
+    $this->assertEqual(theme_get_setting('override', $name), 'base');
+  }
+
+  /**
+   * Tests enabling a sub-theme.
+   */
+  function testEnableSubTheme() {
+    $name = 'test_subtheme';
+    $base_name = 'test_basetheme';
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertFalse(array_keys($themes));
+
+    $this->themeHandler()->enable(array($name));
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertTrue(isset($themes[$name]));
+    $this->assertTrue(isset($themes[$base_name]));
+
+    $this->themeHandler()->disable(array($name));
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertFalse(isset($themes[$name]));
+    $this->assertTrue(isset($themes[$base_name]));
+  }
+
+  /**
+   * Tests enabling a non-existing theme.
+   */
+  function testEnableNonExisting() {
+    $name = 'non_existing_theme';
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertFalse(array_keys($themes));
+
+    try {
+      $message = 'ThemeHandler::enable() throws InvalidArgumentException upon enabling a non-existing theme.';
+      $this->themeHandler()->enable(array($name));
+      $this->fail($message);
+    }
+    catch (\InvalidArgumentException $e) {
+      $this->pass(get_class($e) . ': ' . $e->getMessage());
+    }
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertFalse(array_keys($themes));
+  }
+
+  /**
+   * Tests enabling a theme with a too long name.
+   */
+  function testEnableNameTooLong() {
+    $name = 'test_theme_having_veery_long_name_which_is_too_long';
+
+    try {
+      $message = 'ThemeHandler::enable() throws ExtensionNameLengthException upon enabling a theme with a too long name.';
+      $this->themeHandler()->enable(array($name));
+      $this->fail($message);
+    }
+    catch (ExtensionNameLengthException $e) {
+      $this->pass(get_class($e) . ': ' . $e->getMessage());
+    }
+  }
+
+  /**
+   * Tests disabling a theme.
+   */
+  function testDisable() {
+    $name = 'test_basetheme';
+    $this->themeHandler()->enable(array($name));
+
+    // Prime the relevant drupal_static()s.
+    $this->assertEqual(array_keys(system_list('theme')), array($name));
+    $this->assertIdentical(theme_get_setting('features.favicon', $name), FALSE);
+
+    $this->themeHandler()->disable(array($name));
+
+    $this->assertIdentical($this->extensionConfig()->get('theme'), array());
+    $this->assertIdentical($this->extensionConfig()->get("disabled.theme.$name"), 0);
+
+    $this->assertFalse(array_keys($this->themeHandler()->listInfo()));
+    $this->assertFalse(array_keys(system_list('theme')));
+
+    // Verify that test_basetheme.settings no longer applies, even though the
+    // configuration still exists.
+    $this->assertIdentical(theme_get_setting('features.favicon', $name), TRUE);
+    $this->assertNull(theme_get_setting('base', $name));
+    $this->assertNull(theme_get_setting('override', $name));
+
+    // The theme is not uninstalled, so its configuration must still exist.
+    $this->assertTrue($this->config("$name.settings")->get());
+  }
+
+  /**
+   * Tests disabling and enabling a theme.
+   *
+   * Verifies that
+   * - themes can be re-enabled
+   * - default configuration is not re-imported upon re-enabling an already
+   *   installed theme.
+   */
+  function testDisableEnable() {
+    $name = 'test_basetheme';
+
+    $this->themeHandler()->enable(array($name));
+    $this->themeHandler()->disable(array($name));
+
+    $this->assertIdentical($this->config("$name.settings")->get('base'), 'only');
+    $this->assertIdentical($this->config('system.date_format.fancy')->get('label'), 'Fancy date');
+
+    // Default configuration never overwrites custom configuration, so just
+    // changing values in existing configuration will cause ConfigInstaller to
+    // simply skip those files. To ensure that no default configuration is
+    // re-imported, the custom configuration has to be deleted.
+    $this->configStorage()->delete("$name.settings");
+    $this->configStorage()->delete('system.date_format.fancy');
+    // Reflect direct storage operations in ConfigFactory.
+    $this->container->get('config.factory')->reset();
+
+    $this->themeHandler()->enable(array($name));
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertTrue(isset($themes[$name]));
+    $this->assertEqual($themes[$name]->getName(), $name);
+
+    $this->assertEqual(array_keys(system_list('theme')), array_keys($themes));
+
+    $this->assertFalse($this->config("$name.settings")->get());
+    $this->assertNull($this->config('system.date_format.fancy')->get('label'));
+  }
+
+  /**
+   * Tests disabling the default theme.
+   */
+  function testDisableDefault() {
+    $name = 'stark';
+    $other_name = 'bartik';
+    $this->themeHandler()->enable(array($name, $other_name));
+    $this->themeHandler()->setDefault($name);
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertTrue(isset($themes[$name]));
+    $this->assertTrue(isset($themes[$other_name]));
+
+    try {
+      $message = 'ThemeHandler::disable() throws InvalidArgumentException upon disabling default theme.';
+      $this->themeHandler()->disable(array($name));
+      $this->fail($message);
+    }
+    catch (\InvalidArgumentException $e) {
+      $this->pass(get_class($e) . ': ' . $e->getMessage());
+    }
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertTrue(isset($themes[$name]));
+    $this->assertTrue(isset($themes[$other_name]));
+  }
+
+  /**
+   * Tests disabling the admin theme.
+   */
+  function testDisableAdmin() {
+    $name = 'stark';
+    $other_name = 'bartik';
+    $this->themeHandler()->enable(array($name, $other_name));
+    $this->config('system.theme')->set('admin', $name)->save();
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertTrue(isset($themes[$name]));
+    $this->assertTrue(isset($themes[$other_name]));
+
+    try {
+      $message = 'ThemeHandler::disable() throws InvalidArgumentException upon disabling admin theme.';
+      $this->themeHandler()->disable(array($name));
+      $this->fail($message);
+    }
+    catch (\InvalidArgumentException $e) {
+      $this->pass(get_class($e) . ': ' . $e->getMessage());
+    }
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertTrue(isset($themes[$name]));
+    $this->assertTrue(isset($themes[$other_name]));
+  }
+
+  /**
+   * Tests disabling a sub-theme.
+   */
+  function testDisableSubTheme() {
+    $name = 'test_subtheme';
+    $base_name = 'test_basetheme';
+
+    $this->themeHandler()->enable(array($name));
+    $this->themeHandler()->disable(array($name));
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertFalse(isset($themes[$name]));
+    $this->assertTrue(isset($themes[$base_name]));
+  }
+
+  /**
+   * Tests disabling a base theme before its sub-theme.
+   */
+  function testDisableBaseBeforeSubTheme() {
+    $name = 'test_basetheme';
+    $sub_name = 'test_subtheme';
+
+    $this->themeHandler()->enable(array($sub_name));
+
+    try {
+      $message = 'ThemeHandler::disable() throws InvalidArgumentException upon disabling base theme before sub theme.';
+      $this->themeHandler()->disable(array($name));
+      $this->fail($message);
+    }
+    catch (\InvalidArgumentException $e) {
+      $this->pass(get_class($e) . ': ' . $e->getMessage());
+    }
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertTrue(isset($themes[$name]));
+    $this->assertTrue(isset($themes[$sub_name]));
+
+    // Verify that disabling both at the same time works.
+    $this->themeHandler()->disable(array($name, $sub_name));
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertFalse(isset($themes[$name]));
+    $this->assertFalse(isset($themes[$sub_name]));
+  }
+
+  /**
+   * Tests disabling a non-existing theme.
+   */
+  function testDisableNonExisting() {
+    $name = 'non_existing_theme';
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertFalse(array_keys($themes));
+
+    try {
+      $message = 'ThemeHandler::disable() throws InvalidArgumentException upon disabling a non-existing theme.';
+      $this->themeHandler()->disable(array($name));
+      $this->fail($message);
+    }
+    catch (\InvalidArgumentException $e) {
+      $this->pass(get_class($e) . ': ' . $e->getMessage());
+    }
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertFalse(array_keys($themes));
+  }
+
+  /**
+   * Tests that theme info can be altered by a module.
+   *
+   * @see module_test_system_info_alter()
+   */
+  function testThemeInfoAlter() {
+    $name = 'seven';
+    $this->container->get('state')->set('module_test.hook_system_info_alter', TRUE);
+    $module_handler = $this->container->get('module_handler');
+
+    $this->themeHandler()->enable(array($name));
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertFalse(isset($themes[$name]->info['regions']['test_region']));
+
+    $module_handler->install(array('module_test'), FALSE);
+    $this->assertTrue($module_handler->moduleExists('module_test'));
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertTrue(isset($themes[$name]->info['regions']['test_region']));
+
+    // Legacy assertions.
+    // @todo Remove once theme initialization/info has been modernized.
+    // @see https://drupal.org/node/2228093
+    $info = system_get_info('theme', $name);
+    $this->assertTrue(isset($info['regions']['test_region']));
+    $regions = system_region_list($name);
+    $this->assertTrue(isset($regions['test_region']));
+    $system_list = system_list('theme');
+    $this->assertTrue(isset($system_list[$name]->info['regions']['test_region']));
+
+    $module_handler->uninstall(array('module_test'));
+    $this->assertFalse($module_handler->moduleExists('module_test'));
+
+    $themes = $this->themeHandler()->listInfo();
+    $this->assertFalse(isset($themes[$name]->info['regions']['test_region']));
+
+    // Legacy assertions.
+    // @todo Remove once theme initialization/info has been modernized.
+    // @see https://drupal.org/node/2228093
+    $info = system_get_info('theme', $name);
+    $this->assertFalse(isset($info['regions']['test_region']));
+    $regions = system_region_list($name);
+    $this->assertFalse(isset($regions['test_region']));
+    $system_list = system_list('theme');
+    $this->assertFalse(isset($system_list[$name]->info['regions']['test_region']));
+  }
+
+  /**
+   * Returns the theme handler service.
+   *
+   * @return \Drupal\Core\Extension\ThemeHandlerInterface
+   */
+  protected function themeHandler() {
+    return $this->container->get('theme_handler');
+  }
+
+  /**
+   * Returns the system.theme config object.
+   *
+   * @return \Drupal\Core\Config\Config
+   */
+  protected function extensionConfig() {
+    return $this->config('core.extension');
+  }
+
+  /**
+   * Returns a given config object.
+   *
+   * @param string $name
+   *   The name of the config object to load.
+   *
+   * @return \Drupal\Core\Config\Config
+   */
+  protected function config($name) {
+    return $this->container->get('config.factory')->get($name);
+  }
+
+  /**
+   * Returns the active configuration storage.
+   *
+   * @return \Drupal\Core\Config\ConfigStorageInterface
+   */
+  protected function configStorage() {
+    return $this->container->get('config.storage');
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php
index 03f8ca1cbd3483c135c9dd5560057d71d1f3843a..3779f2578cb31e90b2274fcb7703f1bff9a41140 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php
@@ -35,13 +35,6 @@ class MenuRouterTest extends WebTestBase {
    */
   protected $default_theme;
 
-  /**
-   * Name of an alternate theme to use for tests.
-   *
-   * @var string
-   */
-  protected $alternate_theme;
-
   public static function getInfo() {
     return array(
       'name' => 'Menu router',
@@ -359,42 +352,32 @@ public function testAuthUserUserLogin() {
    * Tests theme integration.
    */
   public function testThemeIntegration() {
-    $this->initializeTestThemeConfiguration();
+    $this->default_theme = 'bartik';
+    $this->admin_theme = 'seven';
+
+    $theme_handler = $this->container->get('theme_handler');
+    $theme_handler->enable(array($this->default_theme, $this->admin_theme));
+    $this->container->get('config.factory')->get('system.theme')
+      ->set('default', $this->default_theme)
+      ->set('admin', $this->admin_theme)
+      ->save();
+    $theme_handler->disable(array('stark'));
+
     $this->doTestThemeCallbackMaintenanceMode();
 
-    $this->initializeTestThemeConfiguration();
     $this->doTestThemeCallbackFakeTheme();
 
-    $this->initializeTestThemeConfiguration();
     $this->doTestThemeCallbackAdministrative();
 
-    $this->initializeTestThemeConfiguration();
     $this->doTestThemeCallbackNoThemeRequested();
 
-    $this->initializeTestThemeConfiguration();
     $this->doTestThemeCallbackOptionalTheme();
   }
 
-  /**
-   * Explicitly set the default and admin themes.
-   */
-  protected function initializeTestThemeConfiguration() {
-    $this->default_theme = 'bartik';
-    $this->admin_theme = 'seven';
-    $this->alternate_theme = 'stark';
-    theme_enable(array($this->default_theme));
-    \Drupal::config('system.theme')
-      ->set('default', $this->default_theme)
-      ->set('admin', $this->admin_theme)
-      ->save();
-    theme_disable(array($this->alternate_theme));
-  }
-
   /**
    * Test the theme negotiation when it is set to use an administrative theme.
    */
   protected function doTestThemeCallbackAdministrative() {
-    theme_enable(array($this->admin_theme));
     $this->drupalGet('menu-test/theme-callback/use-admin-theme');
     $this->assertText('Active theme: seven. Actual theme: seven.', 'The administrative theme can be correctly set in a theme negotiation.');
     $this->assertRaw('seven/style.css', "The administrative theme's CSS appears on the page.");
@@ -405,7 +388,6 @@ protected function doTestThemeCallbackAdministrative() {
    */
   protected function doTestThemeCallbackMaintenanceMode() {
     $this->container->get('state')->set('system.maintenance_mode', TRUE);
-    theme_enable(array($this->admin_theme));
 
     // For a regular user, the fact that the site is in maintenance mode means
     // we expect the theme callback system to be bypassed entirely.
@@ -432,10 +414,14 @@ protected function doTestThemeCallbackOptionalTheme() {
     $this->assertRaw('bartik/css/style.css', "The default theme's CSS appears on the page.");
 
     // Now enable the theme and request it again.
-    theme_enable(array($this->alternate_theme));
+    $theme_handler = $this->container->get('theme_handler');
+    $theme_handler->enable(array('stark'));
+
     $this->drupalGet('menu-test/theme-callback/use-stark-theme');
     $this->assertText('Active theme: stark. Actual theme: stark.', 'The theme negotiation system uses an optional theme once it has been enabled.');
     $this->assertRaw('stark/css/layout.css', "The optional theme's CSS appears on the page.");
+
+    $theme_handler->disable(array('stark'));
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/System/InfoAlterTest.php b/core/modules/system/lib/Drupal/system/Tests/System/InfoAlterTest.php
index 5175b7f89a3017392a19a9225fcaa35a3253c0c3..31387d0558fdb71ab8256b7e36217d159e4dbbd4 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/InfoAlterTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/InfoAlterTest.php
@@ -7,12 +7,15 @@
 
 namespace Drupal\system\Tests\System;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\simpletest\DrupalUnitTestBase;
 
 /**
  * Tests the effectiveness of hook_system_info_alter().
  */
-class InfoAlterTest extends WebTestBase {
+class InfoAlterTest extends DrupalUnitTestBase {
+
+  public static $modules = array('system');
+
   public static function getInfo() {
     return array(
       'name' => 'System info alter',
@@ -32,24 +35,12 @@ function testSystemInfoAlter() {
     \Drupal::state()->set('module_test.hook_system_info_alter', TRUE);
     $info = system_rebuild_module_data();
     $this->assertFalse(isset($info['node']->info['required']), 'Before the module_test is installed the node module is not required.');
-    // Enable seven and the test module.
-    theme_enable(array('seven'));
+
+    // Enable the test module.
     \Drupal::moduleHandler()->install(array('module_test'), FALSE);
     $this->assertTrue(\Drupal::moduleHandler()->moduleExists('module_test'), 'Test module is enabled.');
 
-    // Verify that the rebuilt and altered theme info is returned.
-    $info = system_get_info('theme', 'seven');
-    $this->assertTrue(isset($info['regions']['test_region']), 'Altered theme info was returned by system_get_info().');
-    $seven_regions = system_region_list('seven');
-    $this->assertTrue(isset($seven_regions['test_region']), 'Altered theme info was returned by system_region_list().');
-    $system_list_themes = system_list('theme');
-    $info = $system_list_themes['seven']->info;
-    $this->assertTrue(isset($info['regions']['test_region']), 'Altered theme info was returned by system_list().');
-    $list_themes = list_themes();
-    $this->assertTrue(isset($list_themes['seven']->info['regions']['test_region']), 'Altered theme info was returned by list_themes().');
-    system_list_reset();
     $info = system_rebuild_module_data();
     $this->assertTrue($info['node']->info['required'], 'After the module_test is installed the node module is required.');
-    \Drupal::state()->set('module_test.hook_system_info_alter', FALSE);
   }
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php b/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php
index 520cb116595e129e588975b20faa856cb24b9e85..c7911b5565c7e4f61b7aebaddfe35b6a8914aa10 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php
@@ -178,7 +178,7 @@ function testThemeSettings() {
    * Test the administration theme functionality.
    */
   function testAdministrationTheme() {
-    theme_enable(array('bartik', 'seven'));
+    $this->container->get('theme_handler')->enable(array('seven'));
 
     // Enable an administration theme and show it on the node admin pages.
     $edit = array(
@@ -212,9 +212,6 @@ function testAdministrationTheme() {
     $this->assertRaw('core/themes/stark', 'Site default theme used on the add content page.');
 
     // Reset to the default theme settings.
-    \Drupal::config('system.theme')
-      ->set('default', 'bartik')
-      ->save();
     $edit = array(
       'admin_theme' => '0',
       'use_admin_theme' => FALSE,
@@ -222,10 +219,10 @@ function testAdministrationTheme() {
     $this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
 
     $this->drupalGet('admin');
-    $this->assertRaw('core/themes/bartik', 'Site default theme used on administration page.');
+    $this->assertRaw('core/themes/stark', 'Site default theme used on administration page.');
 
     $this->drupalGet('node/add');
-    $this->assertRaw('core/themes/bartik', 'Site default theme used on the add content page.');
+    $this->assertRaw('core/themes/stark', 'Site default theme used on the add content page.');
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php
index 76edbcda1c411249999c7b7bf97035e5fbbf6541..7d5e16e15b638f492d377113beb296fd434ef9df 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php
@@ -202,11 +202,13 @@ function testFunctionOverride() {
    * Test the list_themes() function.
    */
   function testListThemes() {
-    $themes = list_themes();
+    $theme_handler = $this->container->get('theme_handler');
+    $theme_handler->enable(array('test_subtheme'));
+    $themes = $theme_handler->listInfo();
+
     // Check if drupal_theme_access() retrieves enabled themes properly from list_themes().
     $this->assertTrue(drupal_theme_access('test_theme'), 'Enabled theme detected');
-    // Check if list_themes() returns disabled themes.
-    $this->assertTrue(array_key_exists('test_basetheme', $themes), 'Disabled theme detected');
+
     // Check for base theme and subtheme lists.
     $base_theme_list = array('test_basetheme' => 'Theme test base theme');
     $sub_theme_list = array('test_subtheme' => 'Theme test subtheme');
@@ -223,6 +225,7 @@ function testListThemes() {
    * Test the theme_get_setting() function.
    */
   function testThemeGetSetting() {
+    $this->container->get('theme_handler')->enable(array('test_subtheme'));
     $GLOBALS['theme_key'] = 'test_theme';
     $this->assertIdentical(theme_get_setting('theme_test_setting'), 'default value', 'theme_get_setting() uses the default theme automatically.');
     $this->assertNotEqual(theme_get_setting('subtheme_override', 'test_basetheme'), theme_get_setting('subtheme_override', 'test_subtheme'), 'Base theme\'s default settings values can be overridden by subtheme.');
@@ -279,29 +282,4 @@ function testPreprocessHtml() {
     $this->assertText('theme test page bottom markup', 'Modules are able to set the page bottom region.');
   }
 
-  /**
-   * Test that themes can be disabled programmatically but admin theme and default theme can not.
-   */
-  function testDisableTheme() {
-    // Enable Bartik, Seven and Stark.
-    \Drupal::service('theme_handler')->enable(array('bartik', 'seven', 'stark'));
-
-    // Set Bartik as the default theme and Seven as the admin theme.
-    \Drupal::config('system.theme')
-      ->set('default', 'bartik')
-      ->set('admin', 'seven')
-      ->save();
-
-    $theme_list = array_keys(\Drupal::service('theme_handler')->listInfo());
-    // Attempt to disable all themes. theme_disable() ensures that the default
-    // theme and the admin theme will not be disabled.
-    \Drupal::service('theme_handler')->disable($theme_list);
-
-    $theme_list = \Drupal::service('theme_handler')->listInfo();
-
-    // Ensure Bartik and Seven are still enabled and Stark is disabled.
-    $this->assertTrue($theme_list['bartik']->status == 1, 'Default theme is enabled.');
-    $this->assertTrue($theme_list['seven']->status == 1, 'Admin theme is enabled.');
-    $this->assertTrue($theme_list['stark']->status == 0, 'Stark is disabled.');
-  }
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigSettingsTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigSettingsTest.php
index e33f79b341fd1a751a0b9f24fb640fc7b582a56b..6dff9acfa672482f2190996a69581c3ed5427a8f 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigSettingsTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigSettingsTest.php
@@ -77,23 +77,21 @@ function testTwigDebugOverride() {
    */
   function testTwigCacheOverride() {
     $extension = twig_extension();
-    theme_enable(array('test_theme'));
-    \Drupal::config('system.theme')
-      ->set('default', 'test_theme')
-      ->save();
-
-    // Unset the global variables, so \Drupal\Core\Theme\Registry::init() fires
-    // drupal_theme_initialize, which fills up the global variables  properly
-    // and chosen the current active theme.
-    unset($GLOBALS['theme_info']);
-    unset($GLOBALS['theme']);
+    $theme_handler = $this->container->get('theme_handler');
+    $theme_handler->enable(array('test_theme'));
+    $theme_handler->setDefault('test_theme');
+
+    // The registry still works on theme globals, so set them here.
+    $GLOBALS['theme'] = 'test_theme';
+    $GLOBALS['theme_info'] = $theme_handler->listInfo()['test_theme'];
+
     // Reset the theme registry, so that the new theme is used.
     $this->container->set('theme.registry', NULL);
 
     // Load array of Twig templates.
+    // reset() is necessary to invalidate caches tagged with 'theme_registry'.
     $registry = $this->container->get('theme.registry');
     $registry->reset();
-
     $templates = $registry->getRuntime();
 
     // Get the template filename and the cache filename for
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index ca5d12806f59bff08653c6e944506c47120a8ed2..421491af066ebeb7e39cf17e063b1350fe1fa501 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -567,13 +567,6 @@ function system_requirements($phase) {
  * Implements hook_install().
  */
 function system_install() {
-  // Enable and set the default theme. Can't use theme_enable() this early in
-  // installation.
-  \Drupal::service('config.installer')->installDefaultConfig('theme', 'stark');
-  \Drupal::config('system.theme')
-    ->set('default', 'stark')
-    ->save();
-
   // Populate the cron key state variable.
   $cron_key = Crypt::randomBytesBase64(55);
   \Drupal::state()->set('system.cron_key', $cron_key);
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index d719b5a8a7b2c1698805e1431c07b0d2256454ae..50cd7966997deec24f58535010ea2f1544fab1cf 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1289,52 +1289,17 @@ function system_rebuild_module_data() {
   return $modules_cache;
 }
 
-/**
- * Helper function to scan and collect theme .info.yml data and their engines.
- *
- * @return \Drupal\Core\Extension\Extension[]
- *   An associative array of themes information.
- *
- * @see \Drupal\Core\Extension\ThemeHandlerInterface::rebuildThemeData()
- *
- * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
- *   Use \Drupal::service('theme_handler')->rebuildThemeData().
- */
-function _system_rebuild_theme_data() {
-  return \Drupal::service('theme_handler')->rebuildThemeData();
-}
-
 /**
  * Rebuild, save, and return data about all currently available themes.
  *
  * @return \Drupal\Core\Extension\Extension[]
  *   Array of all available themes and their data.
+ *
+ * @deprecated 8.x
+ *   Use \Drupal::service('theme_handler')->rebuildThemeData().
  */
 function system_rebuild_theme_data() {
-  $themes = _system_rebuild_theme_data();
-  ksort($themes);
-  // @todo This function has no business in determining/setting the status of
-  //   a theme, but various other functions expect it to return themes with a
-  //   $status property. system_list() stores the return value of this function
-  //   in state, and ensures to set/override the $status property for each theme
-  //   based on the current config. Remove this code when themes have a proper
-  //   installation status.
-  // @see http://drupal.org/node/1067408
-  $enabled_themes = \Drupal::config('core.extension')->get('theme') ?: array();
-  $files = array();
-  foreach ($themes as $name => $theme) {
-    $theme->status = (int) isset($enabled_themes[$name]);
-    $files[$name] = $theme->getPathname();
-  }
-  // Replace last known theme data state.
-  // @todo Obsolete with proper installation status for themes.
-  \Drupal::state()->set('system.theme.data', $themes);
-
-  // Store filenames to allow system_list() and drupal_get_filename() to
-  // retrieve them without having to rebuild or scan the filesystem.
-  \Drupal::state()->set('system.theme.files', $files);
-
-  return $themes;
+  return \Drupal::service('theme_handler')->rebuildThemeData();
 }
 
 /**
@@ -1357,22 +1322,24 @@ function _system_default_theme_features() {
 /**
  * Get a list of available regions from a specified theme.
  *
- * @param $theme_key
- *   The name of a theme.
+ * @param \Drupal\Core\Extension\Extension|string $theme
+ *   A theme extension object, or the name of a theme.
  * @param $show
  *   Possible values: REGIONS_ALL or REGIONS_VISIBLE. Visible excludes hidden
  *   regions.
  * @return
  *   An array of regions in the form $region['name'] = 'description'.
  */
-function system_region_list($theme_key, $show = REGIONS_ALL) {
-  $themes = list_themes();
-  if (!isset($themes[$theme_key])) {
-    return array();
+function system_region_list($theme, $show = REGIONS_ALL) {
+  if (!$theme instanceof Extension) {
+    $themes = \Drupal::service('theme_handler')->listInfo();
+    if (!isset($themes[$theme])) {
+      return array();
+    }
+    $theme = $themes[$theme];
   }
-
   $list = array();
-  $info = $themes[$theme_key]->info;
+  $info = $theme->info;
   // If requested, suppress hidden regions. See block_admin_display_form().
   foreach ($info['regions'] as $name => $label) {
     if ($show == REGIONS_ALL || !isset($info['regions_hidden']) || !in_array($name, $info['regions_hidden'])) {
diff --git a/core/modules/system/tests/themes/test_basetheme/config/install/system.date_format.fancy.yml b/core/modules/system/tests/themes/test_basetheme/config/install/system.date_format.fancy.yml
new file mode 100644
index 0000000000000000000000000000000000000000..12ae88611714d774538bea9d7bbf211e6d2e4caa
--- /dev/null
+++ b/core/modules/system/tests/themes/test_basetheme/config/install/system.date_format.fancy.yml
@@ -0,0 +1,11 @@
+# Themes are not supposed to provide/install this kind of config normally.
+# This exists for testing purposes only.
+# @see \Drupal\system\Tests\Extension\ThemeHandlerTest
+id: fancy
+label: 'Fancy date'
+status: true
+langcode: en
+locked: false
+pattern:
+  php: 'U'
+  intl: 'EEEE, LLLL d, yyyy - kk:mm'
diff --git a/core/modules/system/tests/themes/test_basetheme/config/install/test_basetheme.settings.yml b/core/modules/system/tests/themes/test_basetheme/config/install/test_basetheme.settings.yml
index 2d5de349cedae1a27e23c0dba539e6f70294fb92..4a06f9ac63ce3045174a79375492c4faee0ca70b 100644
--- a/core/modules/system/tests/themes/test_basetheme/config/install/test_basetheme.settings.yml
+++ b/core/modules/system/tests/themes/test_basetheme/config/install/test_basetheme.settings.yml
@@ -1,3 +1,4 @@
 features:
   favicon: false
 base: only
+override: base
diff --git a/core/modules/system/tests/themes/test_basetheme/config/schema/test_basetheme.schema.yml b/core/modules/system/tests/themes/test_basetheme/config/schema/test_basetheme.schema.yml
index 8a4bd452a974b005432d475f3dffd902711ab6bc..e22c3c5be75348b5b270ffbdad0d1b8628d2e517 100644
--- a/core/modules/system/tests/themes/test_basetheme/config/schema/test_basetheme.schema.yml
+++ b/core/modules/system/tests/themes/test_basetheme/config/schema/test_basetheme.schema.yml
@@ -5,3 +5,6 @@ test_basetheme.settings:
     base:
       type: string
       label: 'Base theme setting'
+    override:
+      type: string
+      label: 'Whether the setting has been overridden'
diff --git a/core/modules/system/tests/themes/test_subtheme/config/install/test_subtheme.settings.yml b/core/modules/system/tests/themes/test_subtheme/config/install/test_subtheme.settings.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0589cab093869febf68a48c25a9c319379334b90
--- /dev/null
+++ b/core/modules/system/tests/themes/test_subtheme/config/install/test_subtheme.settings.yml
@@ -0,0 +1 @@
+override: sub
diff --git a/core/modules/system/tests/themes/test_subtheme/config/schema/test_subtheme.schema.yml b/core/modules/system/tests/themes/test_subtheme/config/schema/test_subtheme.schema.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9c7b0ce3403c1ccc8c63c9e27f80c84a2255dde1
--- /dev/null
+++ b/core/modules/system/tests/themes/test_subtheme/config/schema/test_subtheme.schema.yml
@@ -0,0 +1,7 @@
+test_subtheme.settings:
+  type: theme_settings
+  label: 'Test sub theme settings'
+  mapping:
+    override:
+      type: string
+      label: 'Whether the setting has been overridden'
diff --git a/core/modules/system/tests/themes/test_theme_having_veery_long_name_which_is_too_long/test_theme_having_veery_long_name_which_is_too_long.info.yml b/core/modules/system/tests/themes/test_theme_having_veery_long_name_which_is_too_long/test_theme_having_veery_long_name_which_is_too_long.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fa0a207717f00c6bc85faff46e00c6b310779822
--- /dev/null
+++ b/core/modules/system/tests/themes/test_theme_having_veery_long_name_which_is_too_long/test_theme_having_veery_long_name_which_is_too_long.info.yml
@@ -0,0 +1,5 @@
+type: theme
+core: 8.x
+name: 'Test theme with a too long name'
+version: VERSION
+hidden: true
diff --git a/core/modules/tour/lib/Drupal/tour/Tests/TourTestBasic.php b/core/modules/tour/lib/Drupal/tour/Tests/TourTestBasic.php
index e6da9006f5819daddea6c768a0b6ac915a58b66c..49dafdcb6459b2cd7e0e9183c1cb5a60cb778967 100644
--- a/core/modules/tour/lib/Drupal/tour/Tests/TourTestBasic.php
+++ b/core/modules/tour/lib/Drupal/tour/Tests/TourTestBasic.php
@@ -49,15 +49,13 @@ protected function setUp() {
 
     // Make sure we are using distinct default and administrative themes for
     // the duration of these tests.
+    $this->container->get('theme_handler')->enable(array('bartik', 'seven'));
     $this->container->get('config.factory')
       ->get('system.theme')
       ->set('default', 'bartik')
-      ->save();
-    theme_enable(array('seven'));
-    $this->container->get('config.factory')
-      ->get('system.theme')
       ->set('admin', 'seven')
       ->save();
+
     $this->permissions[] = 'view the administration theme';
 
     //Create an admin user to view tour tips.
diff --git a/core/profiles/standard/config/install/system.theme.yml b/core/profiles/standard/config/install/system.theme.yml
new file mode 100644
index 0000000000000000000000000000000000000000..57dadd47b64187d5b58050af8ff67960253a81aa
--- /dev/null
+++ b/core/profiles/standard/config/install/system.theme.yml
@@ -0,0 +1,2 @@
+admin: seven
+default: bartik
diff --git a/core/profiles/standard/standard.info.yml b/core/profiles/standard/standard.info.yml
index 30f9c261174cccc10769ead292b2a72ce93a7319..3f8116b1805e52059494f7d41a3ca3614d5f9a06 100644
--- a/core/profiles/standard/standard.info.yml
+++ b/core/profiles/standard/standard.info.yml
@@ -35,3 +35,6 @@ dependencies:
   - views
   - views_ui
   - tour
+themes:
+  - bartik
+  - seven
diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install
index 60c6f7204159b79d5a0e16fbe82ae7755192356a..73ea0749a23cafb7ede46cda8699636ac7820951 100644
--- a/core/profiles/standard/standard.install
+++ b/core/profiles/standard/standard.install
@@ -14,15 +14,6 @@
  * @see system_install()
  */
 function standard_install() {
-  // Enable Bartik theme and set it as default theme instead of Stark.
-  // @see system_install()
-  $default_theme = 'bartik';
-  \Drupal::config('system.theme')
-    ->set('default', $default_theme)
-    ->save();
-  theme_enable(array($default_theme));
-  theme_disable(array('stark'));
-
   // Set front page to "node".
   \Drupal::config('system.site')->set('page.front', 'node')->save();
 
@@ -78,7 +69,5 @@ function standard_install() {
   $shortcut->save();
 
   // Enable the admin theme.
-  theme_enable(array('seven'));
-  \Drupal::config('system.theme')->set('admin', 'seven')->save();
   \Drupal::config('node.settings')->set('use_admin_theme', '1')->save();
 }
diff --git a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
index b9637fef8f948deabfcdad9a3c0df795c1854dba..cdbd0f65fcbd449016414869e69e3a92983865d4 100644
--- a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
@@ -11,6 +11,8 @@
 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;
 
 /**
@@ -38,11 +40,11 @@ class ThemeHandlerTest extends UnitTestCase {
   protected $infoParser;
 
   /**
-   * The mocked cache backend.
+   * The mocked state backend.
    *
-   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   * @var \Drupal\Core\State\StateInterface|\PHPUnit_Framework_MockObject_MockObject
    */
-  protected $cacheBackend;
+  protected $state;
 
   /**
    * The mocked config factory.
@@ -104,7 +106,7 @@ protected function setUp() {
       ),
     ));
     $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
-    $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    $this->state = new State(new KeyValueMemoryFactory());
     $this->infoParser = $this->getMock('Drupal\Core\Extension\InfoParserInterface');
     $this->configInstaller = $this->getMock('Drupal\Core\Config\ConfigInstallerInterface');
     $this->routeBuilder = $this->getMockBuilder('Drupal\Core\Routing\RouteBuilder')
@@ -113,131 +115,10 @@ protected function setUp() {
     $this->extensionDiscovery = $this->getMockBuilder('Drupal\Core\Extension\ExtensionDiscovery')
       ->disableOriginalConstructor()
       ->getMock();
-    $this->themeHandler = new TestThemeHandler($this->configFactory, $this->moduleHandler, $this->cacheBackend, $this->infoParser, $this->configInstaller, $this->routeBuilder, $this->extensionDiscovery);
+    $this->themeHandler = new TestThemeHandler($this->configFactory, $this->moduleHandler, $this->state, $this->infoParser, $this->configInstaller, $this->routeBuilder, $this->extensionDiscovery);
 
-    $this->getContainerWithCacheBins($this->cacheBackend);
-  }
-
-  /**
-   * Tests enabling a theme with a name longer than 50 chars.
-   *
-   * @expectedException \Drupal\Core\Extension\ExtensionNameLengthException
-   * @expectedExceptionMessage Theme name <em class="placeholder">thisNameIsFarTooLong0000000000000000000000000000051</em> is over the maximum allowed length of 50 characters.
-   */
-  public function testThemeEnableWithTooLongName() {
-    $this->themeHandler->enable(array('thisNameIsFarTooLong0000000000000000000000000000051'));
-  }
-
-  /**
-   * Tests enabling a single theme.
-   *
-   * @see \Drupal\Core\Extension\ThemeHandler::enable()
-   */
-  public function testEnableSingleTheme() {
-    $theme_list = array('theme_test');
-
-    $this->configFactory->get('core.extension')
-      ->expects($this->once())
-      ->method('set')
-      ->with('theme.theme_test', 0)
-      ->will($this->returnSelf());
-    $this->configFactory->get('core.extension')
-      ->expects($this->once())
-      ->method('save');
-
-    $this->configFactory->get('core.extension')
-      ->expects($this->once())
-      ->method('clear')
-      ->with('disabled.theme.theme_test')
-      ->will($this->returnSelf());
-    $this->configFactory->get('core.extension')
-      ->expects($this->once())
-      ->method('save');
-
-    $this->extensionDiscovery->expects($this->any())
-      ->method('scan')
-      ->will($this->returnValue(array()));
-
-    // Ensure that the themes_enabled hook is fired.
-    $this->moduleHandler->expects($this->at(0))
-      ->method('invokeAll')
-      ->with('themes_enabled', array($theme_list));
-
-    // Ensure the config installer will be called.
-    $this->configInstaller->expects($this->once())
-      ->method('installDefaultConfig')
-      ->with('theme', $theme_list[0]);
-
-    $this->themeHandler->enable($theme_list);
-
-    $this->assertTrue($this->themeHandler->clearedCssCache);
-    $this->assertTrue($this->themeHandler->registryRebuild);
-  }
-
-  /**
-   * Ensures that enabling a theme does clear the theme info listing.
-   *
-   * @see \Drupal\Core\Extension\ThemeHandler::listInfo()
-   */
-  public function testEnableAndListInfo() {
-    $this->configFactory->get('core.extension')
-      ->expects($this->exactly(2))
-      ->method('set')
-      ->will($this->returnSelf());
-
-    $this->configFactory->get('core.extension')
-      ->expects($this->exactly(2))
-      ->method('clear')
-      ->will($this->returnSelf());
-
-    $this->extensionDiscovery->expects($this->any())
-      ->method('scan')
-      ->will($this->returnValue(array()));
-
-    $this->themeHandler->enable(array('bartik'));
-    $this->themeHandler->systemList['bartik'] = new Extension('theme', DRUPAL_ROOT . '/core/themes/bartik/bartik.info.yml', 'bartik.info.yml');
-    $this->themeHandler->systemList['bartik']->info = array(
-      'stylesheets' => array(
-        'all' => array(
-          'css/layout.css',
-          'css/style.css',
-          'css/colors.css',
-        ),
-      ),
-      'libraries' => array(
-        'example/theme',
-      ),
-      'engine' => 'twig',
-      'base theme' => 'stark',
-    );
-
-    $list_info = $this->themeHandler->listInfo();
-    $this->assertCount(1, $list_info);
-
-    $this->assertEquals($this->themeHandler->systemList['bartik']->info['stylesheets'], $list_info['bartik']->stylesheets);
-    $this->assertEquals($this->themeHandler->systemList['bartik']->libraries, $list_info['bartik']->libraries);
-    $this->assertEquals('twig', $list_info['bartik']->engine);
-    $this->assertEquals('stark', $list_info['bartik']->base_theme);
-    $this->assertEquals(0, $list_info['bartik']->status);
-
-    $this->themeHandler->systemList['seven'] = new Extension('theme', DRUPAL_ROOT . '/core/themes/seven/seven.info.yml', 'seven.info.yml');
-    $this->themeHandler->systemList['seven']->info = array(
-      'stylesheets' => array(
-        'screen' => array(
-          'style.css',
-        ),
-      ),
-      'libraries' => array(),
-    );
-    $this->themeHandler->systemList['seven']->status = 1;
-
-    $this->themeHandler->enable(array('seven'));
-
-    $list_info = $this->themeHandler->listInfo();
-    $this->assertCount(2, $list_info);
-
-    $this->assertEquals($this->themeHandler->systemList['seven']->info['stylesheets'], $list_info['seven']->stylesheets);
-    $this->assertEquals(1, $list_info['seven']->status);
+    $cache_backend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    $this->getContainerWithCacheBins($cache_backend);
   }
 
   /**
@@ -265,6 +146,9 @@ public function testRebuildThemeData() {
         $info_parser = new InfoParser();
         return $info_parser->parse($file);
       }));
+    $this->moduleHandler->expects($this->once())
+      ->method('buildModuleDependencies')
+      ->will($this->returnArgument(0));
 
     $this->moduleHandler->expects($this->once())
       ->method('alter');
@@ -328,6 +212,9 @@ public function testRebuildThemeDataWithThemeParents() {
         $info_parser = new InfoParser();
         return $info_parser->parse($file);
       }));
+    $this->moduleHandler->expects($this->once())
+      ->method('buildModuleDependencies')
+      ->will($this->returnArgument(0));
 
     $theme_data = $this->themeHandler->rebuildThemeData();
     $this->assertCount(2, $theme_data);