diff --git a/core/core.services.yml b/core/core.services.yml
index 9e82af7..9dc6b10 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -511,6 +511,12 @@ services:
   extension.list.profile:
     class: Drupal\Core\Extension\ProfileExtensionList
     arguments: ['@app.root', 'profile', '@cache.default', '@info_parser', '@module_handler', '@state', '%install_profile%']
+  extension.list.theme:
+    class: Drupal\Core\Extension\ThemeExtensionList
+    arguments: ['@app.root', 'theme', '@cache.default', '@info_parser', '@module_handler', '@state', '@config.factory', '@extension.list.theme_engine', '%install_profile%']
+  extension.list.theme_engine:
+    class: Drupal\Core\Extension\ThemeEngineExtensionList
+    arguments: ['@app.root', 'theme_engine', '@cache.default', '@info_parser', '@module_handler', '@state', '%install_profile%']
   content_uninstall_validator:
     class: Drupal\Core\Entity\ContentUninstallValidator
     tags:
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index a67d7ff..fd3cf6f 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -226,45 +226,24 @@ function drupal_get_filename($type, $name, $filename = NULL) {
     return 'core/core.info.yml';
   }
 
-  if ($type === 'module' || $type === 'profile') {
-    $service_id = 'extension.list.' . $type;
-    /** @var \Drupal\Core\Extension\ExtensionList $extension_list */
-    $extension_list = \Drupal::service($service_id);
-    if (isset($filename)) {
-      // Manually add the info file path of an extension.
-      $extension_list->setPathname($name, $filename);
-    }
-    try {
-      return $extension_list->getPathname($name);
-    }
-    catch (\InvalidArgumentException $e) {
-      // Catch the exception. This will result in triggering an error.
-    }
+  $service_id = 'extension.list.' . $type;
+  /** @var \Drupal\Core\Extension\ExtensionList $extension_list */
+  $extension_list = \Drupal::service($service_id);
+  if (isset($filename)) {
+    // Manually add the info file path of an extension.
+    $extension_list->setPathname($name, $filename);
   }
-  else {
-
-    if (!isset($files[$type])) {
-      $files[$type] = [];
-    }
-
-    if (isset($filename)) {
-      $files[$type][$name] = $filename;
-    }
-    elseif (!isset($files[$type][$name])) {
-      // If still unknown, retrieve the file list prepared in state by
-      // \Drupal\Core\Extension\ExtensionList() and
-      // \Drupal\Core\Extension\ThemeHandlerInterface::rebuildThemeData().
-      if (!isset($files[$type][$name]) && \Drupal::hasService('state')) {
-        $files[$type] += \Drupal::state()->get('system.' . $type . '.files', []);
-      }
-    }
-
-    if (isset($files[$type][$name])) {
-      return $files[$type][$name];
-    }
+  try {
+    return $extension_list->getPathname($name);
+  }
+  catch (\InvalidArgumentException $e) {
+    // Catch the exception. This will result in triggering an error.
+    // If the filename is still unknown, create a user-level error message.
+    trigger_error(SafeMarkup::format('The following @type is missing from the file system: @name', [
+      '@type' => $type,
+      '@name' => $name
+    ]), E_USER_WARNING);
   }
-  // If the filename is still unknown, create a user-level error message.
-  trigger_error(SafeMarkup::format('The following @type is missing from the file system: @name', ['@type' => $type, '@name' => $name]), E_USER_WARNING);
 }
 
 /**
diff --git a/core/includes/module.inc b/core/includes/module.inc
index 63b3abc..6b03302 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -5,6 +5,7 @@
  * API for loading and interacting with Drupal modules.
  */
 
+use Drupal\Core\Extension\Extension;
 use Drupal\Core\Extension\ExtensionDiscovery;
 
 /**
@@ -14,7 +15,7 @@
  *   The type of list to return:
  *   - theme: All installed themes.
  *
- * @return
+ * @return array
  *   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.
@@ -22,43 +23,19 @@
  * @see \Drupal\Core\Extension\ThemeHandler::listInfo()
  */
 function system_list($type) {
-  $lists = &drupal_static(__FUNCTION__);
-  if ($cached = \Drupal::cache('bootstrap')->get('system_list')) {
-    $lists = $cached->data;
-  }
-  else {
-    $lists = [
-      'theme' => [],
-      'filepaths' => [],
-    ];
-    // ThemeHandler maintains the 'system.theme.data' state record.
-    $theme_data = \Drupal::state()->get('system.theme.data', []);
-    foreach ($theme_data as $name => $theme) {
-      $lists['theme'][$name] = $theme;
-      $lists['filepaths'][] = [
-        'type' => 'theme',
-        'name' => $name,
-        'filepath' => $theme->getPathname(),
-      ];
-    }
-    \Drupal::cache('bootstrap')->set('system_list', $lists);
-  }
-  // To avoid a separate database lookup for the filepath, prime the
-  // drupal_get_filename() static cache with all enabled themes.
-  foreach ($lists['filepaths'] as $item) {
-    system_register($item['type'], $item['name'], $item['filepath']);
-  }
-
-  return $lists[$type];
+  return array_filter(\Drupal::service('extension.list.' . $type)->getList(), function (Extension $extension) {
+    return $extension->status;
+  });
 }
 
 /**
  * Resets all system_list() caches.
  */
 function system_list_reset() {
-  drupal_static_reset('system_list');
+  \Drupal::service('extension.list.profile')->reset();
   \Drupal::service('extension.list.module')->reset();
-  \Drupal::cache('bootstrap')->delete('system_list');
+  \Drupal::service('extension.list.theme_engine')->reset();
+  \Drupal::service('extension.list.theme')->reset();
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Extension/ExtensionList.php b/core/lib/Drupal/Core/Extension/ExtensionList.php
index 6201a08..744648b 100644
--- a/core/lib/Drupal/Core/Extension/ExtensionList.php
+++ b/core/lib/Drupal/Core/Extension/ExtensionList.php
@@ -314,6 +314,9 @@ protected function doList() {
       // Merge extension type-specific defaults.
       $extension->info += $this->defaults;
 
+      // Add extension-type-specific information.
+      $this->alterExtensionInfo($extension);
+
       // Invoke hook_system_info_alter() to give installed modules a chance to
       // modify the data in the .info.yml files if necessary.
       $this->moduleHandler->alter('system_info', $extension->info, $extension, $this->type);
@@ -540,4 +543,12 @@ public function getPath($extension_name) {
     return dirname($this->getPathname($extension_name));
   }
 
+  /**
+   * Allows sub-classes to alter the information from .info.yml files
+   *
+   * @param \Drupal\Core\Extension\Extension $extension
+   *   The extension whose info is to be altered.
+   */
+  protected function alterExtensionInfo(Extension $extension) {}
+
 }
diff --git a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
index 01fcf59..2eea314 100644
--- a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
+++ b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
@@ -9,6 +9,8 @@
 
 /**
  * Provides a list of available modules.
+ *
+ * @internal
  */
 class ModuleExtensionList extends ExtensionList {
 
diff --git a/core/lib/Drupal/Core/Extension/ProfileExtensionList.php b/core/lib/Drupal/Core/Extension/ProfileExtensionList.php
index bb388b2..dfb922f 100644
--- a/core/lib/Drupal/Core/Extension/ProfileExtensionList.php
+++ b/core/lib/Drupal/Core/Extension/ProfileExtensionList.php
@@ -4,6 +4,8 @@
 
 /**
  * Provides a list of installation profiles.
+ *
+ * @internal
  */
 class ProfileExtensionList extends ExtensionList {
 
diff --git a/core/lib/Drupal/Core/Extension/ProfileExtensionList.php b/core/lib/Drupal/Core/Extension/ThemeEngineExtensionList.php
similarity index 70%
copy from core/lib/Drupal/Core/Extension/ProfileExtensionList.php
copy to core/lib/Drupal/Core/Extension/ThemeEngineExtensionList.php
index bb388b2..5a897f1 100644
--- a/core/lib/Drupal/Core/Extension/ProfileExtensionList.php
+++ b/core/lib/Drupal/Core/Extension/ThemeEngineExtensionList.php
@@ -3,9 +3,11 @@
 namespace Drupal\Core\Extension;
 
 /**
- * Provides a list of installation profiles.
+ * Provides a list of available theme engines.
+ *
+ * @internal
  */
-class ProfileExtensionList extends ExtensionList {
+class ThemeEngineExtensionList extends ExtensionList {
 
   /**
    * {@inheritdoc}
@@ -22,7 +24,7 @@ class ProfileExtensionList extends ExtensionList {
    * {@inheritdoc}
    */
   protected function getInstalledExtensionNames() {
-    return [$this->installProfile];
+    return ['twig'];
   }
 
 }
diff --git a/core/lib/Drupal/Core/Extension/ThemeExtensionList.php b/core/lib/Drupal/Core/Extension/ThemeExtensionList.php
new file mode 100644
index 0000000..0e88d61
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/ThemeExtensionList.php
@@ -0,0 +1,272 @@
+<?php
+
+namespace Drupal\Core\Extension;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\State\StateInterface;
+
+/**
+ * Provides a list of available themes.
+ *
+ * @internal
+ */
+class ThemeExtensionList extends ExtensionList {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaults = [
+    'engine' => 'twig',
+    'base theme' => 'stable',
+    'regions' => [
+      'sidebar_first' => 'Left sidebar',
+      'sidebar_second' => 'Right sidebar',
+      'content' => 'Content',
+      'header' => 'Header',
+      'primary_menu' => 'Primary menu',
+      'secondary_menu' => 'Secondary menu',
+      'footer' => 'Footer',
+      'highlighted' => 'Highlighted',
+      'help' => 'Help',
+      'page_top' => 'Page top',
+      'page_bottom' => 'Page bottom',
+      'breadcrumb' => 'Breadcrumb',
+    ],
+    'description' => '',
+    'features' => [
+      'favicon',
+      'logo',
+      'node_user_picture',
+      'comment_user_picture',
+      'comment_user_verification',
+    ],
+    'screenshot' => 'screenshot.png',
+    'php' => DRUPAL_MINIMUM_PHP,
+    'libraries' => [],
+    'libraries_extend' => [],
+    'libraries_override' => [],
+  ];
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * The theme engine list needed by this theme list.
+   *
+   * @var \Drupal\Core\Extension\ExtensionList
+   */
+  protected $engineList;
+
+  /**
+   * The list of installed themes.
+   *
+   * @var string[]
+   */
+  protected $installedThemes = [];
+
+  /**
+   * Constructs a new ThemeExtensionList instance.
+   *
+   * @param string $root
+   *   The app root.
+   * @param string $type
+   *   The extension type.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
+   *   The cache.
+   * @param \Drupal\Core\Extension\InfoParserInterface $info_parser
+   *   The info parser.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state service.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory.
+   * @param \Drupal\Core\Extension\ThemeEngineExtensionList $engine_list
+   *   The theme engine extension listing.
+   * @param string $install_profile
+   *   The install profile used by the site.
+   */
+  public function __construct($root, $type, CacheBackendInterface $cache, InfoParserInterface $info_parser, ModuleHandlerInterface $module_handler, StateInterface $state, ConfigFactoryInterface $config_factory, ThemeEngineExtensionList $engine_list, $install_profile) {
+    parent::__construct($root, $type, $cache, $info_parser, $module_handler, $state, $install_profile);
+
+    $this->configFactory = $config_factory;
+    $this->engineList = $engine_list;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doList() {
+    // Find themes.
+    $themes = parent::doList();
+
+    $engines = $this->engineList->getList();
+    // Always get the freshest list of themes (rather than the already cached
+    // list in $this->installedThemes) when building the theme listing because a
+    // theme could have just been installed or uninstalled.
+    $this->installedThemes = $this->configFactory->get('core.extension')->get('theme') ?: [];
+
+    $sub_themes = [];
+    // Read info files for each theme.
+    foreach ($themes as $name => $theme) {
+      // Defaults to 'twig' (see self::defaults above).
+      $engine = $theme->info['engine'];
+      if (isset($engines[$engine])) {
+        $theme->owner = $engines[$engine]->getExtensionPathname();
+        $theme->prefix = $engines[$engine]->getName();
+      }
+      // Add this theme as a sub-theme if it has a base theme.
+      if (!empty($theme->info['base theme'])) {
+        $sub_themes[] = $name;
+      }
+      // Add weight and status.
+      $theme->status = (int) isset($this->installedThemes[$name]);
+      $theme->weight = isset($this->installedThemes[$name]) ? $this->installedThemes[$name] : 0;
+    }
+
+    // Build dependencies.
+    $themes = $this->moduleHandler->buildModuleDependencies($themes);
+
+    // After establishing the full list of available themes, fill in data for
+    // sub-themes.
+    $this->fillInSubThemeData($themes, $sub_themes);
+
+    return $themes;
+  }
+
+  /**
+   * Fills in data for themes that are also sub-themes.
+   *
+   * @param array $themes
+   *   The array of partly processed theme information.
+   * @param array $sub_themes
+   *   A list of themes from the $theme array that are also sub-themes.
+   */
+  protected function fillInSubThemeData(array &$themes, array $sub_themes) {
+    foreach ($sub_themes as $name) {
+      $sub_theme = $themes[$name];
+      // The $base_themes property is optional; only set for sub themes.
+      // @see ThemeHandlerInterface::listInfo()
+      $sub_theme->base_themes = $this->getBaseThemes($themes, $name);
+      // empty() cannot be used here, since static::doGetBaseThemes() adds
+      // the key of a base theme with a value of NULL in case it is not found,
+      // in order to prevent needless iterations.
+      if (!current($sub_theme->base_themes)) {
+        continue;
+      }
+      // Determine the root base theme.
+      $root_key = key($sub_theme->base_themes);
+      // Build the list of sub-themes for each of the theme's base themes.
+      foreach (array_keys($sub_theme->base_themes) as $base_theme) {
+        $themes[$base_theme]->sub_themes[$name] = $sub_theme->info['name'];
+      }
+      // Add the theme engine info from the root base theme.
+      if (isset($themes[$root_key]->owner)) {
+        $sub_theme->info['engine'] = $themes[$root_key]->info['engine'];
+        $sub_theme->owner = $themes[$root_key]->owner;
+        $sub_theme->prefix = $themes[$root_key]->prefix;
+      }
+    }
+  }
+
+  /**
+   * Finds all the base themes for the specified theme.
+   *
+   * Themes can inherit templates and function implementations from earlier
+   * themes.
+   *
+   * @param \Drupal\Core\Extension\Extension[] $themes
+   *   An array of available themes.
+   * @param string $theme
+   *   The name of the theme whose base we are looking for.
+   *
+   * @return array
+   *   Returns an array of all of the theme's ancestors; the first element's
+   *   value will be NULL if an error occurred.
+   */
+  public function getBaseThemes(array $themes, $theme) {
+    return $this->doGetBaseThemes($themes, $theme);
+  }
+
+  /**
+   * Finds the base themes for the specific theme.
+   *
+   * @param array $themes
+   *   An array of available themes.
+   * @param string $theme
+   *   The name of the theme whose base we are looking for.
+   * @param array $used_themes
+   *   (optional) A recursion parameter preventing endless loops. Defaults to
+   *   an empty array.
+   *
+   * @return array
+   *   An array of base themes.
+   */
+  protected function doGetBaseThemes(array $themes, $theme, array $used_themes = []) {
+    if (!isset($themes[$theme]->info['base theme'])) {
+      return [];
+    }
+
+    $base_key = $themes[$theme]->info['base theme'];
+    // Does the base theme exist?
+    if (!isset($themes[$base_key])) {
+      return [$base_key => NULL];
+    }
+
+    $current_base_theme = [$base_key => $themes[$base_key]->info['name']];
+
+    // Is the base theme itself a child of another theme?
+    if (isset($themes[$base_key]->info['base theme'])) {
+      // Do we already know the base themes of this theme?
+      if (isset($themes[$base_key]->base_themes)) {
+        return $themes[$base_key]->base_themes + $current_base_theme;
+      }
+      // Prevent loops.
+      if (!empty($used_themes[$base_key])) {
+        return [$base_key => NULL];
+      }
+      $used_themes[$base_key] = TRUE;
+      return $this->doGetBaseThemes($themes, $base_key, $used_themes) + $current_base_theme;
+    }
+    // If we get here, then this is our parent theme.
+    return $current_base_theme;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function alterExtensionInfo(Extension $extension) {
+    // Remove the default Stable base theme when 'base theme: false' is set in
+    // a theme .info.yml file.
+    if ($extension->info['base theme'] === FALSE) {
+      unset($extension->info['base theme']);
+    }
+
+    if (!empty($extension->info['base theme'])) {
+      // Add the base theme as a proper dependency.
+      $extension->info['dependencies'][] = $extension->info['base theme'];
+    }
+
+    // Prefix screenshot with theme path.
+    if (!empty($extension->info['screenshot'])) {
+      $extension->info['screenshot'] = $extension->getPath() . '/' . $extension->info['screenshot'];
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getInstalledExtensionNames() {
+    // Cache the installed themes to avoid multiple calls to the config system.
+    if (!isset($this->installedThemes)) {
+      $this->installedThemes = $this->configFactory->get('core.extension')->get('theme') ?: [];
+    }
+    return array_keys($this->installedThemes);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php
index d54ff1f..a608b86 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandler.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php
@@ -11,21 +11,6 @@
 class ThemeHandler implements ThemeHandlerInterface {
 
   /**
-   * Contains the features enabled for themes by default.
-   *
-   * @var array
-   *
-   * @see _system_default_theme_features()
-   */
-  protected $defaultFeatures = [
-    'favicon',
-    'logo',
-    'node_user_picture',
-    'comment_user_picture',
-    'comment_user_verification',
-  ];
-
-  /**
    * A list of all currently available themes.
    *
    * @var array
@@ -84,9 +69,9 @@ class ThemeHandler implements ThemeHandlerInterface {
   /**
    * An extension discovery instance.
    *
-   * @var \Drupal\Core\Extension\ExtensionDiscovery
+   * @var \Drupal\Core\Extension\ThemeExtensionList
    */
-  protected $extensionDiscovery;
+  protected $themeList;
 
   /**
    * The CSS asset collection optimizer service.
@@ -122,16 +107,16 @@ class ThemeHandler implements ThemeHandlerInterface {
    *   The state store.
    * @param \Drupal\Core\Extension\InfoParserInterface $info_parser
    *   The info parser to parse the theme.info.yml files.
-   * @param \Drupal\Core\Extension\ExtensionDiscovery $extension_discovery
+   * @param \Drupal\Core\Extension\ThemeExtensionList $theme_list
    *   (optional) A extension discovery instance (for unit tests).
    */
-  public function __construct($root, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, InfoParserInterface $info_parser, ExtensionDiscovery $extension_discovery = NULL) {
+  public function __construct($root, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, InfoParserInterface $info_parser, ThemeExtensionList $theme_list = NULL) {
     $this->root = $root;
     $this->configFactory = $config_factory;
     $this->moduleHandler = $module_handler;
     $this->state = $state;
     $this->infoParser = $info_parser;
-    $this->extensionDiscovery = $extension_discovery;
+    $this->themeList = $theme_list;
   }
 
   /**
@@ -179,16 +164,11 @@ public function uninstall(array $theme_list) {
   public function listInfo() {
     if (!isset($this->list)) {
       $this->list = [];
-      $themes = $this->systemThemeList();
-      // @todo Ensure that systemThemeList() does not contain an empty list
-      //   during the batch installer, see https://www.drupal.org/node/2322619.
-      if (empty($themes)) {
-        $this->refreshInfo();
-        $this->list = $this->list ?: [];
-        $themes = \Drupal::state()->get('system.theme.data', []);
-      }
+      $themes = $this->getThemeExtensionList()->getList();
       foreach ($themes as $theme) {
-        $this->addTheme($theme);
+        if ($theme->status) {
+          $this->addTheme($theme);
+        }
       }
     }
     return $this->list;
@@ -216,32 +196,16 @@ public function addTheme(Extension $theme) {
    * {@inheritdoc}
    */
   public function refreshInfo() {
-    $extension_config = $this->configFactory->get('core.extension');
-    $installed = $extension_config->get('theme');
-    // Only refresh the info if a theme has been installed. Modules are
-    // installed before themes by the installer and this method is called during
-    // module installation.
-    if (empty($installed) && empty($this->list)) {
-      return;
-    }
-
-    $this->reset();
-    // @todo Avoid re-scanning all themes by retaining the original (unaltered)
-    //   theme info somewhere.
-    $list = $this->rebuildThemeData();
-    foreach ($list as $name => $theme) {
-      if (isset($installed[$name])) {
-        $this->addTheme($theme);
-      }
-    }
-    $this->state->set('system.theme.data', $this->list);
+    $this->getThemeExtensionList()->reset();
+    $this->list = NULL;
+    return $this->listInfo();
   }
 
   /**
    * {@inheritdoc}
    */
   public function reset() {
-    $this->systemListReset();
+    $this->getThemeExtensionList()->reset();
     $this->list = NULL;
   }
 
@@ -249,186 +213,27 @@ public function reset() {
    * {@inheritdoc}
    */
   public function rebuildThemeData() {
-    $listing = $this->getExtensionDiscovery();
-    $themes = $listing->scan('theme');
-    $engines = $listing->scan('theme_engine');
-    $extension_config = $this->configFactory->get('core.extension');
-    $installed = $extension_config->get('theme') ?: [];
-
-    // Set defaults for theme info.
-    $defaults = [
-      'engine' => 'twig',
-      'base theme' => 'stable',
-      'regions' => [
-        'sidebar_first' => 'Left sidebar',
-        'sidebar_second' => 'Right sidebar',
-        'content' => 'Content',
-        'header' => 'Header',
-        'primary_menu' => 'Primary menu',
-        'secondary_menu' => 'Secondary menu',
-        'footer' => 'Footer',
-        'highlighted' => 'Highlighted',
-        'help' => 'Help',
-        'page_top' => 'Page top',
-        'page_bottom' => 'Page bottom',
-        'breadcrumb' => 'Breadcrumb',
-      ],
-      'description' => '',
-      'features' => $this->defaultFeatures,
-      'screenshot' => 'screenshot.png',
-      'php' => DRUPAL_MINIMUM_PHP,
-      'libraries' => [],
-    ];
-
-    $sub_themes = [];
-    $files_theme = [];
-    $files_theme_engine = [];
-    // 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($installed[$key]);
-
-      $theme->info = $this->infoParser->parse($theme->getPathname()) + $defaults;
-      // Remove the default Stable base theme when 'base theme: false' is set in
-      // a theme .info.yml file.
-      if ($theme->info['base theme'] === FALSE) {
-        unset($theme->info['base theme']);
-      }
-
-      // Add the info file modification time, so it becomes available for
-      // contributed modules to use for ordering theme lists.
-      $theme->info['mtime'] = $theme->getMTime();
-
-      // Invoke hook_system_info_alter() to give installed modules a chance to
-      // modify the data in the .info.yml files if necessary.
-      // @todo Remove $type argument, obsolete with $theme->getType().
-      $type = 'theme';
-      $this->moduleHandler->alter('system_info', $theme->info, $theme, $type);
-
-      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).
-      $engine = $theme->info['engine'];
-      if (isset($engines[$engine])) {
-        $theme->owner = $engines[$engine]->getExtensionPathname();
-        $theme->prefix = $engines[$engine]->getName();
-        $files_theme_engine[$engine] = $engines[$engine]->getPathname();
-      }
-
-      // Prefix screenshot with theme path.
-      if (!empty($theme->info['screenshot'])) {
-        $theme->info['screenshot'] = $theme->getPath() . '/' . $theme->info['screenshot'];
-      }
-
-      $files_theme[$key] = $theme->getPathname();
-    }
-    // Build dependencies.
-    // @todo Move into a generic ExtensionHandler base class.
-    // @see https://www.drupal.org/node/2208429
-    $themes = $this->moduleHandler->buildModuleDependencies($themes);
-
-    // Store filenames to allow system_list() and drupal_get_filename() to
-    // retrieve them for themes and theme engines without having to scan the
-    // filesystem.
-    $this->state->set('system.theme.files', $files_theme);
-    $this->state->set('system.theme_engine.files', $files_theme_engine);
-
-    // After establishing the full list of available themes, fill in data for
-    // sub-themes.
-    foreach ($sub_themes as $key) {
-      $sub_theme = $themes[$key];
-      // The $base_themes property is optional; only set for sub themes.
-      // @see ThemeHandlerInterface::listInfo()
-      $sub_theme->base_themes = $this->getBaseThemes($themes, $key);
-      // empty() cannot be used here, since ThemeHandler::doGetBaseThemes() adds
-      // the key of a base theme with a value of NULL in case it is not found,
-      // in order to prevent needless iterations.
-      if (!current($sub_theme->base_themes)) {
-        continue;
-      }
-      // Determine the root base theme.
-      $root_key = key($sub_theme->base_themes);
-      // Build the list of sub-themes for each of the theme's base themes.
-      foreach (array_keys($sub_theme->base_themes) as $base_theme) {
-        $themes[$base_theme]->sub_themes[$key] = $sub_theme->info['name'];
-      }
-      // Add the theme engine info from the root base theme.
-      if (isset($themes[$root_key]->owner)) {
-        $sub_theme->info['engine'] = $themes[$root_key]->info['engine'];
-        $sub_theme->owner = $themes[$root_key]->owner;
-        $sub_theme->prefix = $themes[$root_key]->prefix;
-      }
-    }
-
-    return $themes;
+    return $this->getThemeExtensionList()->reset()->getList();
   }
 
   /**
    * {@inheritdoc}
    */
   public function getBaseThemes(array $themes, $theme) {
-    return $this->doGetBaseThemes($themes, $theme);
+    return $this->getThemeExtensionList()->getBaseThemes($themes, $theme);
   }
 
   /**
-   * Finds the base themes for the specific theme.
+   * Returns the theme listing service.
    *
-   * @param array $themes
-   *   An array of available themes.
-   * @param string $theme
-   *   The name of the theme whose base we are looking for.
-   * @param array $used_themes
-   *   (optional) A recursion parameter preventing endless loops. Defaults to
-   *   an empty array.
-   *
-   * @return array
-   *   An array of base themes.
-   */
-  protected function doGetBaseThemes(array $themes, $theme, $used_themes = []) {
-    if (!isset($themes[$theme]->info['base theme'])) {
-      return [];
-    }
-
-    $base_key = $themes[$theme]->info['base theme'];
-    // Does the base theme exist?
-    if (!isset($themes[$base_key])) {
-      return [$base_key => NULL];
-    }
-
-    $current_base_theme = [$base_key => $themes[$base_key]->info['name']];
-
-    // Is the base theme itself a child of another theme?
-    if (isset($themes[$base_key]->info['base theme'])) {
-      // Do we already know the base themes of this theme?
-      if (isset($themes[$base_key]->base_themes)) {
-        return $themes[$base_key]->base_themes + $current_base_theme;
-      }
-      // Prevent loops.
-      if (!empty($used_themes[$base_key])) {
-        return [$base_key => NULL];
-      }
-      $used_themes[$base_key] = TRUE;
-      return $this->doGetBaseThemes($themes, $base_key, $used_themes) + $current_base_theme;
-    }
-    // If we get here, then this is our parent theme.
-    return $current_base_theme;
-  }
-
-  /**
-   * Returns an extension discovery object.
-   *
-   * @return \Drupal\Core\Extension\ExtensionDiscovery
+   * @return \Drupal\Core\Extension\ThemeExtensionList
    *   The extension discovery object.
    */
-  protected function getExtensionDiscovery() {
-    if (!isset($this->extensionDiscovery)) {
-      $this->extensionDiscovery = new ExtensionDiscovery($this->root);
+  protected function getThemeExtensionList() {
+    if (!isset($this->themeList)) {
+      $this->themeList = \Drupal::service('extension.list.theme');
     }
-    return $this->extensionDiscovery;
+    return $this->themeList;
   }
 
   /**
@@ -443,23 +248,6 @@ public function getName($theme) {
   }
 
   /**
-   * Wraps system_list_reset().
-   */
-  protected function systemListReset() {
-    system_list_reset();
-  }
-
-  /**
-   * Wraps system_list().
-   *
-   * @return array
-   *   A list of themes keyed by name.
-   */
-  protected function systemThemeList() {
-    return system_list('theme');
-  }
-
-  /**
    * {@inheritdoc}
    */
   public function getThemeDirectories() {
diff --git a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
index a82be6a..d5ae438 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
@@ -56,8 +56,8 @@ public function uninstall(array $theme_list);
    *
    * @return \Drupal\Core\Extension\Extension[]
    *   An associative array of the currently installed themes. The keys are the
-   *   themes' machine names and the values are objects having the following
-   *   properties:
+   *   themes' machine names and the values are Extension objects having the
+   *   following properties:
    *   - filename: The filepath and name of the .info.yml file.
    *   - name: The machine name of the theme.
    *   - status: 1 for installed, 0 for uninstalled themes.
diff --git a/core/lib/Drupal/Core/Extension/ThemeInstaller.php b/core/lib/Drupal/Core/Extension/ThemeInstaller.php
index 43a5469..832395d 100644
--- a/core/lib/Drupal/Core/Extension/ThemeInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ThemeInstaller.php
@@ -62,6 +62,13 @@ class ThemeInstaller implements ThemeInstallerInterface {
   protected $logger;
 
   /**
+   * The theme listing service.
+   *
+   * @var \Drupal\Core\Extension\ThemeExtensionList
+   */
+  protected $themeList;
+
+  /**
    * Constructs a new ThemeInstaller.
    *
    * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
@@ -86,7 +93,7 @@ class ThemeInstaller implements ThemeInstallerInterface {
    * @param \Drupal\Core\State\StateInterface $state
    *   The state store.
    */
-  public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryInterface $config_factory, ConfigInstallerInterface $config_installer, ModuleHandlerInterface $module_handler, ConfigManagerInterface $config_manager, AssetCollectionOptimizerInterface $css_collection_optimizer, RouteBuilderInterface $route_builder, LoggerInterface $logger, StateInterface $state) {
+  public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryInterface $config_factory, ConfigInstallerInterface $config_installer, ModuleHandlerInterface $module_handler, ConfigManagerInterface $config_manager, AssetCollectionOptimizerInterface $css_collection_optimizer, RouteBuilderInterface $route_builder, LoggerInterface $logger, StateInterface $state, ThemeExtensionList $theme_list = NULL) {
     $this->themeHandler = $theme_handler;
     $this->configFactory = $config_factory;
     $this->configInstaller = $config_installer;
@@ -96,6 +103,7 @@ public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryI
     $this->routeBuilder = $route_builder;
     $this->logger = $logger;
     $this->state = $state;
+    $this->themeList = $theme_list;
   }
 
   /**
@@ -178,17 +186,12 @@ public function install(array $theme_list, $install_dependencies = TRUE) {
       $theme_data[$key]->status = 1;
       $this->themeHandler->addTheme($theme_data[$key]);
 
-      // Update the current theme data accordingly.
-      $current_theme_data = $this->state->get('system.theme.data', []);
-      $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();
+      // Reset theme listing.
+      $this->getThemeExtensionList()->reset();
 
       // Only install default configuration if this theme has not been installed
       // already.
@@ -244,14 +247,10 @@ public function uninstall(array $theme_list) {
     }
 
     $this->cssCollectionOptimizer->deleteAll();
-    $current_theme_data = $this->state->get('system.theme.data', []);
     foreach ($theme_list as $key) {
       // The value is not used; the weight is ignored for themes currently.
       $extension_config->clear("theme.$key");
 
-      // Update the current theme data accordingly.
-      unset($current_theme_data[$key]);
-
       // Reset theme settings.
       $theme_settings = &drupal_static('theme_get_setting');
       unset($theme_settings[$key]);
@@ -263,11 +262,10 @@ public function uninstall(array $theme_list) {
     // Don't check schema when uninstalling a theme since we are only clearing
     // keys.
     $extension_config->save(TRUE);
-    $this->state->set('system.theme.data', $current_theme_data);
 
-    // @todo Remove system_list().
-    $this->themeHandler->refreshInfo();
+    // Refresh theme info.
     $this->resetSystem();
+    $this->themeHandler->refreshInfo();
 
     $this->moduleHandler->invokeAll('themes_uninstalled', [$theme_list]);
   }
@@ -279,7 +277,7 @@ protected function resetSystem() {
     if ($this->routeBuilder) {
       $this->routeBuilder->setRebuildNeeded();
     }
-    $this->systemListReset();
+    $this->getThemeExtensionList()->reset();
 
     // @todo It feels wrong to have the requirement to clear the local tasks
     //   cache here.
@@ -295,10 +293,16 @@ protected function themeRegistryRebuild() {
   }
 
   /**
-   * Wraps system_list_reset().
+   * Returns the theme listing service.
+   *
+   * @return \Drupal\Core\Extension\ThemeExtensionList
+   *   The extension discovery object.
    */
-  protected function systemListReset() {
-    system_list_reset();
+  protected function getThemeExtensionList() {
+    if (!isset($this->themeList)) {
+      $this->themeList = \Drupal::service('extension.list.theme');
+    }
+    return $this->themeList;
   }
 
 }
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index bb82d4b..54fec42 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -959,35 +959,17 @@ function system_check_directory($form_element, FormStateInterface $form_state) {
  * @see \Drupal\Core\Extension\ThemeHandlerInterface::rebuildThemeData()
  */
 function system_get_info($type, $name = NULL) {
-  if ($type == 'module') {
-    /** @var \Drupal\Core\Extension\ModuleExtensionList $module_list */
-    $module_list = \Drupal::service('extension.list.module');
-    if (isset($name)) {
-      try {
-        return $module_list->getExtensionInfo($name);
-      }
-      catch (\InvalidArgumentException $e) {
-        return [];
-      }
+  /** @var \Drupal\Core\Extension\ExtensionList $extension_list */
+  $extension_list = \Drupal::service('extension.list.' . $type);
+  if (isset($name)) {
+    try {
+      return $extension_list->getExtensionInfo($name);
     }
-    else {
-      return $module_list->getAllInstalledInfo();
-    }
-  }
-  else {
-    // @todo move into ThemeExtensionList https://www.drupal.org/node/2659940
-    $info = [];
-    $list = system_list($type);
-    foreach ($list as $shortname => $item) {
-      if (!empty($item->status)) {
-        $info[$shortname] = $item->info;
-      }
-    }
-    if (isset($name)) {
-      return isset($info[$name]) ? $info[$name] : [];
+    catch (\InvalidArgumentException $e) {
+      return [];
     }
-    return $info;
   }
+  return $extension_list->getAllInstalledInfo();
 }
 
 /**
diff --git a/core/tests/Drupal/Tests/Core/Extension/ThemeExtensionListTest.php b/core/tests/Drupal/Tests/Core/Extension/ThemeExtensionListTest.php
new file mode 100644
index 0000000..1fb44d0
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Extension/ThemeExtensionListTest.php
@@ -0,0 +1,262 @@
+<?php
+
+namespace Drupal\Tests\Core\Extension;
+
+use Drupal\Core\Cache\MemoryBackend;
+use Drupal\Core\Cache\NullBackend;
+use Drupal\Core\Extension\Extension;
+use Drupal\Core\Extension\ExtensionDiscovery;
+use Drupal\Core\Extension\InfoParser;
+use Drupal\Core\Extension\InfoParserInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Extension\ThemeEngineExtensionList;
+use Drupal\Core\Extension\ThemeExtensionList;
+use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
+use Drupal\Core\Lock\NullLockBackend;
+use Drupal\Core\State\State;
+use Drupal\Tests\UnitTestCase;
+use Prophecy\Argument;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Extension\ThemeExtensionList
+ * @group Extension
+ */
+class ThemeExtensionListTest extends UnitTestCase {
+
+  /**
+   * Tests rebuild the theme data with theme parents.
+   */
+  public function testRebuildThemeDataWithThemeParents() {
+    $extension_discovery = $this->prophesize(ExtensionDiscovery::class);
+    $extension_discovery
+      ->scan('theme')
+      ->willReturn([
+        'test_subtheme'  => new Extension($this->root, 'theme', $this->root . '/core/modules/system/tests/themes/test_subtheme/test_subtheme.info.yml', 'test_subtheme.info.yml'),
+        'test_basetheme' => new Extension($this->root, 'theme', $this->root . '/core/modules/system/tests/themes/test_basetheme/test_basetheme.info.yml', 'test_basetheme.info.yml'),
+      ]);
+    $extension_discovery
+      ->scan('theme_engine')
+      ->willReturn([
+        'twig' => new Extension($this->root, 'theme_engine', $this->root . '/core/themes/engines/twig/twig.info.yml', 'twig.engine'),
+      ]);
+
+    // Verify that info parser is called with the specified paths.
+    $info_parser = $this->prophesize(InfoParserInterface::class);
+    $info_parser->parse(Argument::that(function ($path) {
+        return in_array($path, [
+          $this->root . '/core/modules/system/tests/themes/test_subtheme/test_subtheme.info.yml',
+          $this->root . '/core/modules/system/tests/themes/test_basetheme/test_basetheme.info.yml',
+          $this->root . '/core/themes/engines/twig/twig.info.yml',
+        ], TRUE);
+      }))
+      ->shouldBeCalled()
+      ->will(function ($file) {
+        $info_parser = new InfoParser();
+        return $info_parser->parse($file[0]);
+      });
+
+    $module_handler = $this->prophesize(ModuleHandlerInterface::class);
+    $module_handler
+      ->buildModuleDependencies(Argument::type('array'))
+      ->willReturnArgument(0);
+    $module_handler
+      ->alter('system_info', Argument::type('array'), Argument::type(Extension::class), Argument::any())
+      ->shouldBeCalled();
+
+    $state = new State(new KeyValueMemoryFactory(), new MemoryBackend(), new NullLockBackend());
+
+    $theme_engine_list = new TestThemeEngineExtensionList($this->root, 'theme_engine', new NullBackend('test'), $info_parser->reveal(), $module_handler->reveal(), $state, 'testing');
+    $theme_engine_list->setExtensionDiscovery($extension_discovery->reveal());
+
+    $config_factory = $this->getConfigFactoryStub([
+      'core.extension' => [
+        'module' => [],
+        'theme' => [],
+        'disabled' => [
+          'theme' => [],
+        ],
+      ],
+    ]);
+
+    $theme_list = new TestThemeExtensionList($this->root, 'theme', new NullBackend('test'), $info_parser->reveal(), $module_handler->reveal(), $state, $config_factory, $theme_engine_list, 'testing');
+    $theme_list->setExtensionDiscovery($extension_discovery->reveal());
+
+    $theme_data = $theme_list->reset()->getList();
+    $this->assertCount(2, $theme_data);
+
+    $info_basetheme = $theme_data['test_basetheme'];
+    $info_subtheme = $theme_data['test_subtheme'];
+
+    // Ensure some basic properties.
+    $this->assertInstanceOf('Drupal\Core\Extension\Extension', $info_basetheme);
+    $this->assertEquals('test_basetheme', $info_basetheme->getName());
+    $this->assertInstanceOf('Drupal\Core\Extension\Extension', $info_subtheme);
+    $this->assertEquals('test_subtheme', $info_subtheme->getName());
+
+    // Test the parent/child-theme properties.
+    $info_subtheme->info['base theme'] = 'test_basetheme';
+    $info_basetheme->sub_themes = ['test_subtheme'];
+
+    $this->assertEquals($this->root . '/core/themes/engines/twig/twig.engine', $info_basetheme->owner);
+    $this->assertEquals('twig', $info_basetheme->prefix);
+    $this->assertEquals($this->root . '/core/themes/engines/twig/twig.engine', $info_subtheme->owner);
+    $this->assertEquals('twig', $info_subtheme->prefix);
+  }
+
+  /**
+   * Tests getting the base themes for a set a defines themes.
+   *
+   * @param array $themes
+   *   An array of available themes, keyed by the theme name.
+   * @param string $theme
+   *   The theme name to find all its base themes.
+   * @param array $expected
+   *   The expected base themes.
+   *
+   * @dataProvider providerTestGetBaseThemes
+   */
+  public function testGetBaseThemes(array $themes, $theme, array $expected) {
+    // Mocks and stubs.
+    $module_handler = $this->prophesize(ModuleHandlerInterface::class);
+    $state = new State(new KeyValueMemoryFactory(), new MemoryBackend(), new NullLockBackend());
+    $config_factory = $this->getConfigFactoryStub([]);
+    $theme_engine_list = $this->prophesize(ThemeEngineExtensionList::class);
+    $theme_listing = new ThemeExtensionList($this->root, 'theme', new NullBackend('test'), new InfoParser(), $module_handler->reveal(), $state, $config_factory, $theme_engine_list->reveal(), 'test');
+
+    $base_themes = $theme_listing->getBaseThemes($themes, $theme);
+
+    $this->assertEquals($expected, $base_themes);
+  }
+
+  /**
+   * Provides test data for testGetBaseThemes.
+   *
+   * @return array
+   *   An array of theme test data.
+   */
+  public function providerTestGetBaseThemes() {
+    $data = [];
+
+    // Tests a theme without any base theme.
+    $themes = [];
+    $themes['test_1'] = (object) [
+      'name' => 'test_1',
+      'info' => [
+        'name' => 'test_1',
+      ],
+    ];
+    $data[] = [$themes, 'test_1', []];
+
+    // Tests a theme with a non existing base theme.
+    $themes = [];
+    $themes['test_1'] = (object) [
+      'name' => 'test_1',
+      'info' => [
+        'name'       => 'test_1',
+        'base theme' => 'test_2',
+      ],
+    ];
+    $data[] = [$themes, 'test_1', ['test_2' => NULL]];
+
+    // Tests a theme with a single existing base theme.
+    $themes = [];
+    $themes['test_1'] = (object) [
+      'name' => 'test_1',
+      'info' => [
+        'name'       => 'test_1',
+        'base theme' => 'test_2',
+      ],
+    ];
+    $themes['test_2'] = (object) [
+      'name' => 'test_2',
+      'info' => [
+        'name' => 'test_2',
+      ],
+    ];
+    $data[] = [$themes, 'test_1', ['test_2' => 'test_2']];
+
+    // Tests a theme with multiple base themes.
+    $themes = [];
+    $themes['test_1'] = (object) [
+      'name' => 'test_1',
+      'info' => [
+        'name'       => 'test_1',
+        'base theme' => 'test_2',
+      ],
+    ];
+    $themes['test_2'] = (object) [
+      'name' => 'test_2',
+      'info' => [
+        'name'       => 'test_2',
+        'base theme' => 'test_3',
+      ],
+    ];
+    $themes['test_3'] = (object) [
+      'name' => 'test_3',
+      'info' => [
+        'name' => 'test_3',
+      ],
+    ];
+    $data[] = [
+      $themes,
+      'test_1',
+      ['test_2' => 'test_2', 'test_3' => 'test_3'],
+    ];
+
+    return $data;
+  }
+
+}
+
+/**
+ * Trait that allows extension discovery to be set.
+ */
+trait SettableDiscoveryExtensionListTrait {
+
+  /**
+   * The extension discovery for this extension list.
+   *
+   * @var \Drupal\Core\Extension\ExtensionDiscovery
+   */
+  protected $extensionDiscovery;
+
+  /**
+   * Sets the extension discovery.
+   *
+   * @param \Drupal\Core\Extension\ExtensionDiscovery $discovery
+   *   The extension discovery.
+   */
+  public function setExtensionDiscovery(ExtensionDiscovery $discovery) {
+    $this->extensionDiscovery = $discovery;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getExtensionDiscovery() {
+    return $this->extensionDiscovery;
+  }
+
+}
+
+/**
+ * Test theme extension list class.
+ */
+class TestThemeExtensionList extends ThemeExtensionList {
+
+  use SettableDiscoveryExtensionListTrait;
+
+}
+
+/**
+ * Test theme engine extension list class.
+ */
+class TestThemeEngineExtensionList extends ThemeEngineExtensionList {
+
+  use SettableDiscoveryExtensionListTrait;
+
+}
+
+if (!defined('DRUPAL_MINIMUM_PHP')) {
+  define('DRUPAL_MINIMUM_PHP', '5.3.10');
+}
diff --git a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
index 8c105f2..f293e69 100644
--- a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
@@ -9,7 +9,6 @@
 
 use Drupal\Core\Cache\MemoryBackend;
 use Drupal\Core\Extension\Extension;
-use Drupal\Core\Extension\InfoParser;
 use Drupal\Core\Extension\ThemeHandler;
 use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
 use Drupal\Core\Lock\NullLockBackend;
@@ -51,11 +50,11 @@ class ThemeHandlerTest extends UnitTestCase {
   protected $moduleHandler;
 
   /**
-   * The extension discovery.
+   * The theme listing service.
    *
-   * @var \Drupal\Core\Extension\ExtensionDiscovery|\PHPUnit_Framework_MockObject_MockObject
+   * @var \Drupal\Core\Extension\ThemeExtensionList|\PHPUnit_Framework_MockObject_MockObject
    */
-  protected $extensionDiscovery;
+  protected $themeList;
 
   /**
    * The tested theme handler.
@@ -82,10 +81,10 @@ protected function setUp() {
     $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
     $this->state = new State(new KeyValueMemoryFactory(), new MemoryBackend('test'), new NullLockBackend());
     $this->infoParser = $this->getMock('Drupal\Core\Extension\InfoParserInterface');
-    $this->extensionDiscovery = $this->getMockBuilder('Drupal\Core\Extension\ExtensionDiscovery')
+    $this->themeList = $this->getMockBuilder('Drupal\Core\Extension\ThemeExtensionList')
       ->disableOriginalConstructor()
       ->getMock();
-    $this->themeHandler = new StubThemeHandler($this->root, $this->configFactory, $this->moduleHandler, $this->state, $this->infoParser, $this->extensionDiscovery);
+    $this->themeHandler = new StubThemeHandler($this->root, $this->configFactory, $this->moduleHandler, $this->state, $this->infoParser, $this->themeList);
 
     $cache_tags_invalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
     $this->getContainerWithCacheTagsInvalidator($cache_tags_invalidator);
@@ -97,31 +96,14 @@ protected function setUp() {
    * @see \Drupal\Core\Extension\ThemeHandler::rebuildThemeData()
    */
   public function testRebuildThemeData() {
-    $this->extensionDiscovery->expects($this->at(0))
-      ->method('scan')
-      ->with('theme')
+    $this->themeList->expects($this->at(0))
+      ->method('reset')
+      ->willReturnSelf();
+    $this->themeList->expects($this->at(1))
+      ->method('getList')
       ->will($this->returnValue([
         'seven' => new Extension($this->root, 'theme', $this->root . '/core/themes/seven/seven.info.yml', 'seven.theme'),
       ]));
-    $this->extensionDiscovery->expects($this->at(1))
-      ->method('scan')
-      ->with('theme_engine')
-      ->will($this->returnValue([
-        'twig' => new Extension($this->root, 'theme_engine', $this->root . '/core/themes/engines/twig/twig.info.yml', 'twig.engine'),
-      ]));
-    $this->infoParser->expects($this->once())
-      ->method('parse')
-      ->with($this->root . '/core/themes/seven/seven.info.yml')
-      ->will($this->returnCallback(function ($file) {
-        $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');
 
     $theme_data = $this->themeHandler->rebuildThemeData();
     $this->assertCount(1, $theme_data);
@@ -132,11 +114,7 @@ public function testRebuildThemeData() {
     $this->assertEquals('seven', $info->getName());
     $this->assertEquals($this->root . '/core/themes/seven/seven.info.yml', $info->getPathname());
     $this->assertEquals($this->root . '/core/themes/seven/seven.theme', $info->getExtensionPathname());
-    $this->assertEquals($this->root . '/core/themes/engines/twig/twig.engine', $info->owner);
-    $this->assertEquals('twig', $info->prefix);
 
-    $this->assertEquals('twig', $info->info['engine']);
-    $this->assertEquals(['seven/global-styling'], $info->info['libraries']);
   }
 
   /**
@@ -153,158 +131,6 @@ public function testThemeLibrariesEmpty() {
     }
   }
 
-  /**
-   * Tests rebuild the theme data with theme parents.
-   */
-  public function testRebuildThemeDataWithThemeParents() {
-    $this->extensionDiscovery->expects($this->at(0))
-      ->method('scan')
-      ->with('theme')
-      ->will($this->returnValue([
-        'test_subtheme' => new Extension($this->root, 'theme', $this->root . '/core/modules/system/tests/themes/test_subtheme/test_subtheme.info.yml', 'test_subtheme.info.yml'),
-        'test_basetheme' => new Extension($this->root, 'theme', $this->root . '/core/modules/system/tests/themes/test_basetheme/test_basetheme.info.yml', 'test_basetheme.info.yml'),
-      ]));
-    $this->extensionDiscovery->expects($this->at(1))
-      ->method('scan')
-      ->with('theme_engine')
-      ->will($this->returnValue([
-        'twig' => new Extension($this->root, 'theme_engine', $this->root . '/core/themes/engines/twig/twig.info.yml', 'twig.engine'),
-      ]));
-    $this->infoParser->expects($this->at(0))
-      ->method('parse')
-      ->with($this->root . '/core/modules/system/tests/themes/test_subtheme/test_subtheme.info.yml')
-      ->will($this->returnCallback(function ($file) {
-        $info_parser = new InfoParser();
-        return $info_parser->parse($file);
-      }));
-    $this->infoParser->expects($this->at(1))
-      ->method('parse')
-      ->with($this->root . '/core/modules/system/tests/themes/test_basetheme/test_basetheme.info.yml')
-      ->will($this->returnCallback(function ($file) {
-        $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);
-
-    $info_basetheme = $theme_data['test_basetheme'];
-    $info_subtheme = $theme_data['test_subtheme'];
-
-    // Ensure some basic properties.
-    $this->assertInstanceOf('Drupal\Core\Extension\Extension', $info_basetheme);
-    $this->assertEquals('test_basetheme', $info_basetheme->getName());
-    $this->assertInstanceOf('Drupal\Core\Extension\Extension', $info_subtheme);
-    $this->assertEquals('test_subtheme', $info_subtheme->getName());
-
-    // Test the parent/child-theme properties.
-    $info_subtheme->info['base theme'] = 'test_basetheme';
-    $info_basetheme->sub_themes = ['test_subtheme'];
-
-    $this->assertEquals($this->root . '/core/themes/engines/twig/twig.engine', $info_basetheme->owner);
-    $this->assertEquals('twig', $info_basetheme->prefix);
-    $this->assertEquals($this->root . '/core/themes/engines/twig/twig.engine', $info_subtheme->owner);
-    $this->assertEquals('twig', $info_subtheme->prefix);
-  }
-
-  /**
-   * Tests getting the base themes for a set a defines themes.
-   *
-   * @param array $themes
-   *   An array of available themes, keyed by the theme name.
-   * @param string $theme
-   *   The theme name to find all its base themes.
-   * @param array $expected
-   *   The expected base themes.
-   *
-   * @dataProvider providerTestGetBaseThemes
-   */
-  public function testGetBaseThemes(array $themes, $theme, array $expected) {
-    $base_themes = $this->themeHandler->getBaseThemes($themes, $theme);
-    $this->assertEquals($expected, $base_themes);
-  }
-
-  /**
-   * Provides test data for testGetBaseThemes.
-   *
-   * @return array
-   *   An array of theme test data.
-   */
-  public function providerTestGetBaseThemes() {
-    $data = [];
-
-    // Tests a theme without any base theme.
-    $themes = [];
-    $themes['test_1'] = (object) [
-      'name' => 'test_1',
-      'info' => [
-        'name' => 'test_1',
-      ],
-    ];
-    $data[] = [$themes, 'test_1', []];
-
-    // Tests a theme with a non existing base theme.
-    $themes = [];
-    $themes['test_1'] = (object) [
-      'name' => 'test_1',
-      'info' => [
-        'name' => 'test_1',
-        'base theme' => 'test_2',
-      ],
-    ];
-    $data[] = [$themes, 'test_1', ['test_2' => NULL]];
-
-    // Tests a theme with a single existing base theme.
-    $themes = [];
-    $themes['test_1'] = (object) [
-      'name' => 'test_1',
-      'info' => [
-        'name' => 'test_1',
-        'base theme' => 'test_2',
-      ],
-    ];
-    $themes['test_2'] = (object) [
-      'name' => 'test_2',
-      'info' => [
-        'name' => 'test_2',
-      ],
-    ];
-    $data[] = [$themes, 'test_1', ['test_2' => 'test_2']];
-
-    // Tests a theme with multiple base themes.
-    $themes = [];
-    $themes['test_1'] = (object) [
-      'name' => 'test_1',
-      'info' => [
-        'name' => 'test_1',
-        'base theme' => 'test_2',
-      ],
-    ];
-    $themes['test_2'] = (object) [
-      'name' => 'test_2',
-      'info' => [
-        'name' => 'test_2',
-        'base theme' => 'test_3',
-      ],
-    ];
-    $themes['test_3'] = (object) [
-      'name' => 'test_3',
-      'info' => [
-        'name' => 'test_3',
-      ],
-    ];
-    $data[] = [
-      $themes,
-      'test_1',
-      ['test_2' => 'test_2', 'test_3' => 'test_3'],
-    ];
-
-    return $data;
-  }
-
 }
 
 /**
@@ -327,13 +153,6 @@ class StubThemeHandler extends ThemeHandler {
   protected $registryRebuild;
 
   /**
-   * A list of themes keyed by name.
-   *
-   * @var array
-   */
-  protected $systemList;
-
-  /**
    * {@inheritdoc}
    */
   protected function clearCssCache() {
@@ -347,27 +166,8 @@ protected function themeRegistryRebuild() {
     $this->registryRebuild = TRUE;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function systemThemeList() {
-    return $this->systemList;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function systemListReset() {
-  }
-
 }
 
-if (!defined('DRUPAL_EXTENSION_NAME_MAX_LENGTH')) {
-  define('DRUPAL_EXTENSION_NAME_MAX_LENGTH', 50);
-}
-if (!defined('DRUPAL_PHP_FUNCTION_PATTERN')) {
-  define('DRUPAL_PHP_FUNCTION_PATTERN', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*');
-}
 if (!defined('DRUPAL_MINIMUM_PHP')) {
   define('DRUPAL_MINIMUM_PHP', '5.3.10');
 }
