diff --git a/core/core.services.yml b/core/core.services.yml
index b48f1a8..a2d373c 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -384,16 +384,16 @@ services:
     class: Drupal\Core\Routing\Enhancer\AjaxEnhancer
     arguments: ['@content_negotiation']
     tags:
-      - { name: route_enhancer, priority: 20 }
-      - { name: legacy_route_enhancer, priority: 20 }
+      - { name: route_enhancer, priority: 15 }
+      - { name: legacy_route_enhancer, priority: 15 }
   route_enhancer.entity:
     class: Drupal\Core\Entity\Enhancer\EntityRouteEnhancer
-    arguments: ['@content_negotiation']
+    arguments: ['@controller_resolver', '@entity.manager', '@form_builder']
     tags:
-      - { name: route_enhancer, priority: 15 }
+      - { name: route_enhancer, priority: 20 }
   route_enhancer.form:
     class: Drupal\Core\Routing\Enhancer\FormEnhancer
-    arguments: ['@content_negotiation']
+    arguments: ['@service_container', '@controller_resolver', '@form_builder']
     tags:
       - { name: route_enhancer, priority: 10 }
   route_special_attributes_subscriber:
@@ -402,10 +402,16 @@ services:
       - { name: event_subscriber }
   controller.page:
     class: Drupal\Core\Controller\HtmlPageController
-    arguments: ['@http_kernel', '@controller_resolver', '@string_translation', '@title_resolver']
+    arguments: ['@controller_resolver', '@string_translation', '@title_resolver']
+  controller.ajax:
+    class: Drupal\Core\Controller\AjaxController
+    arguments: ['@controller_resolver']
+  controller.entityform:
+    class: Drupal\Core\Entity\HtmlEntityFormController
+    arguments: ['@controller_resolver', '@service_container', '@entity.manager']
   controller.dialog:
     class: Drupal\Core\Controller\DialogController
-    arguments: ['@http_kernel', '@title_resolver']
+    arguments: ['@controller_resolver', '@title_resolver']
   router_listener:
     class: Symfony\Component\HttpKernel\EventListener\RouterListener
     tags:
@@ -418,6 +424,14 @@ services:
     tags:
       - { name: event_subscriber }
     arguments: ['@content_negotiation', '@title_resolver']
+  html_view_subscriber:
+    class: Drupal\Core\EventSubscriber\HtmlViewSubscriber
+    tags:
+      - { name: event_subscriber }
+    arguments: ['@html_page_renderer']
+  html_page_renderer:
+    class: Drupal\Core\Page\DefaultHtmlPageRenderer
+    arguments: ['@language_manager']
   private_key:
     class: Drupal\Core\PrivateKey
     arguments: ['@state']
@@ -447,29 +461,29 @@ services:
   access_check.default:
     class: Drupal\Core\Access\DefaultAccessCheck
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _access }
   access_check.entity:
     class: Drupal\Core\Entity\EntityAccessCheck
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _entity_access }
   access_check.entity_create:
     class: Drupal\Core\Entity\EntityCreateAccessCheck
     arguments: ['@entity.manager']
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _entity_create_access }
   access_check.theme:
     class: Drupal\Core\Theme\ThemeAccessCheck
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _access_theme }
   access_check.custom:
     class: Drupal\Core\Access\CustomAccessCheck
     arguments: ['@controller_resolver']
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _custom_access }
   access_check.csrf:
     class: Drupal\Core\Access\CsrfAccessCheck
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _csrf_token }
     arguments: ['@csrf_token']
   maintenance_mode_subscriber:
     class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber
@@ -521,7 +535,7 @@ services:
     arguments: ['@language_manager', '@string_translation']
   exception_controller:
     class: Drupal\Core\Controller\ExceptionController
-    arguments: ['@content_negotiation']
+    arguments: ['@content_negotiation', '@string_translation', '@title_resolver', '@html_page_renderer']
     calls:
       - [setContainer, ['@service_container']]
   exception_listener:
diff --git a/core/includes/batch.inc b/core/includes/batch.inc
index 049b3fb..597ab43 100644
--- a/core/includes/batch.inc
+++ b/core/includes/batch.inc
@@ -150,30 +150,46 @@ function _batch_progress_page() {
   $batch['url_options']['query']['op'] = $new_op;
 
   $url = url($batch['url'], $batch['url_options']);
-  $element = array(
-    // Redirect through a 'Refresh' meta tag if JavaScript is disabled.
-    '#prefix' => '<noscript>',
-    '#suffix' => '</noscript>',
-    '#tag' => 'meta',
-    '#attributes' => array(
-      'http-equiv' => 'Refresh',
-      'content' => '0; URL=' . $url,
-    ),
-  );
-  drupal_add_html_head($element, 'batch_progress_meta_refresh');
-
-  // Adds JavaScript code and settings for clients where JavaScript is enabled.
-  $js_setting = array(
-    'batch' => array(
-      'errorMessage' => $current_set['error_message'] . '<br />' . $batch['error_message'],
-      'initMessage' => $current_set['init_message'],
-      'uri' => $url,
+
+  $build = array(
+    '#theme' => 'progress_bar',
+    '#percent' => $percentage,
+    '#message' => $message,
+    '#label' => $label,
+    '#attached' => array(
+      'drupal_add_html_head' => array(
+        array(
+          array(
+            // Redirect through a 'Refresh' meta tag if JavaScript is disabled.
+            '#tag' => 'meta',
+            '#noscript' => TRUE,
+            '#attributes' => array(
+              'http-equiv' => 'Refresh',
+              'content' => '0; URL=' . $url,
+            ),
+          ),
+          'batch_progress_meta_refresh',
+        ),
+      ),
+      // Adds JavaScript code and settings for clients where JavaScript is enabled.
+      'js' => array(
+        array(
+          'type' => 'setting',
+          'data' => array(
+            'batch' => array(
+              'errorMessage' => $current_set['error_message'] . '<br />' . $batch['error_message'],
+              'initMessage' => $current_set['init_message'],
+              'uri' => $url,
+            ),
+          ),
+        ),
+      ),
+      'library' => array(
+        array('system', 'drupal.batch'),
+      ),
     ),
   );
-  drupal_add_js($js_setting, 'setting');
-  drupal_add_library('system', 'drupal.batch');
-
-  return theme('progress_bar', array('percent' => $percentage, 'message' => $message, 'label' => $label));
+  return drupal_render($build);
 }
 
 /**
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 6d70110..91d12af 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -418,18 +418,9 @@ function config_get_config_directory($type = CONFIG_ACTIVE_DIRECTORY) {
   global $config_directories;
 
   if (!empty($config_directories[$type])) {
-    // Allow a configuration directory path to be outside of webroot.
-    if (empty($config_directories[$type]['absolute'])) {
-      $path = conf_path() . '/files/' . $config_directories[$type]['path'];
-    }
-    else {
-      $path = $config_directories[$type]['path'];
-    }
+    return $config_directories[$type];
   }
-  else {
-    throw new Exception(format_string('The configuration directory type %type does not exist.', array('%type' => $type)));
-  }
-  return $path;
+  throw new Exception(format_string('The configuration directory type %type does not exist.', array('%type' => $type)));
 }
 
 /**
@@ -2293,7 +2284,7 @@ function _drupal_load_test_overrides($test_prefix) {
   $path_prefix = 'simpletest/' . substr($test_prefix, 10);
   $config_directories = array();
   foreach (array(CONFIG_ACTIVE_DIRECTORY, CONFIG_STAGING_DIRECTORY) as $type) {
-    $config_directories[$type] = array('path' => $path_prefix . '/config_' . $type);
+    $config_directories[$type] = conf_path() . '/files/' . $path_prefix . '/config_' . $type;
   }
 
   // Check for and load a settings.php file in the simpletest files directory.
@@ -2439,7 +2430,7 @@ function language_list($flags = Language::STATE_CONFIGURABLE) {
     $default = language_default();
     if (language_multilingual() || \Drupal::moduleHandler()->moduleExists('language')) {
       // Use language module configuration if available.
-      $language_entities = config_get_storage_names_with_prefix('language.entity');
+      $language_entities = config_get_storage_names_with_prefix('language.entity.');
 
       // Initialize default property so callers have an easy reference and can
       // save the same object without data loss.
diff --git a/core/includes/common.inc b/core/includes/common.inc
index e70816b..aff7665 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -354,7 +354,7 @@ function _drupal_default_html_head() {
 
 /**
  * Retrieves output to be displayed in the HEAD tag of the HTML page.
- */
+  */
 function drupal_get_html_head() {
   $elements = drupal_add_html_head();
   drupal_alter('html_head', $elements);
@@ -3408,7 +3408,12 @@ function drupal_pre_render_html_tag($element) {
     }
     $markup .= '</' . $element['#tag'] . ">\n";
   }
-  $element['#markup'] = $markup;
+  if (!empty($element['#noscript'])) {
+    $element['#markup'] = '<noscript>' . $markup . '</noscript>';
+  }
+  else {
+    $element['#markup'] = $markup;
+  }
   return $element;
 }
 
@@ -3592,7 +3597,7 @@ function drupal_pre_render_dropbutton($element) {
 }
 
 /**
- * Renders the page, including all theming.
+ * Processes the page render array, enhancing it as necessary.
  *
  * @param $page
  *   A string or array representing the content of a page. The array consists of
@@ -3602,10 +3607,13 @@ function drupal_pre_render_dropbutton($element) {
  *   - #show_messages: Suppress drupal_get_message() items. Used by Batch
  *     API (optional).
  *
+ * @return array
+ *   The processed render array for the page.
+ *
  * @see hook_page_alter()
  * @see element_info()
  */
-function drupal_render_page($page) {
+function drupal_prepare_page($page) {
   $main_content_display = &drupal_static('system_main_content_added', FALSE);
 
   // Pull out the page title to set it back later.
@@ -3642,6 +3650,28 @@ function drupal_render_page($page) {
     $page['#title'] = $title;
   }
 
+  return $page;
+}
+
+/**
+ * Renders the page, including all theming.
+ *
+ * @param string|array $page
+ *   A string or array representing the content of a page. The array consists of
+ *   the following keys:
+ *   - #type: Value is always 'page'. This pushes the theming through
+ *     the page template (required).
+ *   - #show_messages: Suppress drupal_get_message() items. Used by Batch
+ *     API (optional).
+ *
+ * @return string
+ *   Returns the rendered string.
+ *
+ * @see hook_page_alter()
+ * @see element_info()
+ */
+function drupal_render_page($page) {
+  $page = drupal_prepare_page($page);
   return drupal_render($page);
 }
 
diff --git a/core/includes/config.inc b/core/includes/config.inc
index af62fda..c7fca03 100644
--- a/core/includes/config.inc
+++ b/core/includes/config.inc
@@ -7,6 +7,7 @@
 use Drupal\Core\Config\Context\FreeConfigContext;
 use Drupal\Core\Config\FileStorage;
 use Drupal\Core\Config\StorageInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Symfony\Component\Yaml\Dumper;
 
 /**
@@ -202,8 +203,8 @@ function config_context_leave() {
  *   Either the entity type name, or NULL if none match.
  */
 function config_get_entity_type_by_name($name) {
-  $entities = array_filter(entity_get_info(), function($entity_info) use ($name) {
-    return (isset($entity_info['config_prefix']) && strpos($name, $entity_info['config_prefix'] . '.') === 0);
+  $entities = array_filter(\Drupal::entityManager()->getDefinitions(), function (EntityTypeInterface $entity_info) use ($name) {
+    return ($config_prefix = $entity_info->getConfigPrefix()) && strpos($name, $config_prefix . '.') === 0;
   });
   return key($entities);
 }
diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index 4dddce1..28d2025 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -266,11 +266,10 @@ function entity_revision_delete($entity_type, $revision_id) {
  * @see \Drupal\Core\Entity\EntityManagerInterface
  */
 function entity_load_by_uuid($entity_type, $uuid, $reset = FALSE) {
-  $entity_info = entity_get_info($entity_type);
-  if (empty($entity_info['entity_keys']['uuid'])) {
+  $entity_info = \Drupal::entityManager()->getDefinition($entity_type);
+  if (!$uuid_key = $entity_info->getKey('uuid')) {
     throw new EntityStorageException("Entity type $entity_type does not support UUIDs.");
   }
-  $uuid_key = $entity_info['entity_keys']['uuid'];
 
   $controller = \Drupal::entityManager()->getStorageController($entity_type);
   if ($reset) {
diff --git a/core/includes/errors.inc b/core/includes/errors.inc
index b7c15b6..03594fe 100644
--- a/core/includes/errors.inc
+++ b/core/includes/errors.inc
@@ -373,12 +373,14 @@ function format_backtrace(array $backtrace) {
     else {
       $call['function'] = 'main';
     }
-    foreach ($trace['args'] as $arg) {
-      if (is_scalar($arg)) {
-        $call['args'][] = is_string($arg) ? '\'' . filter_xss($arg) . '\'' : $arg;
-      }
-      else {
-        $call['args'][] = ucfirst(gettype($arg));
+    if (isset($trace['args'])) {
+      foreach ($trace['args'] as $arg) {
+        if (is_scalar($arg)) {
+          $call['args'][] = is_string($arg) ? '\'' . filter_xss($arg) . '\'' : $arg;
+        }
+        else {
+          $call['args'][] = ucfirst(gettype($arg));
+        }
       }
     }
     $return .= $call['function'] . '(' . implode(', ', $call['args']) . ")\n";
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 8db114a..b44c59a 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -1901,7 +1901,7 @@ function install_already_done_error() {
   global $base_url;
 
   drupal_set_title(t('Drupal already installed'));
-  return t('<ul><li>To start over, you must empty your existing database, delete your active configuration, and copy <em>default.settings.php</em> over <em>settings.php</em>.</li><li>To install to a different database, edit the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</li><li>To locate your active configuration, view the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</em></li></li><li>To upgrade an existing installation, proceed to the <a href="@base-url/core/update.php">update script</a>.</li><li>View your <a href="@base-url">existing site</a>.</li></ul>', array('@base-url' => $base_url));
+  return t('<ul><li>To start over, you must empty your existing database, delete your active configuration, and copy <em>default.settings.php</em> over <em>settings.php</em>.</li><li>To install to a different database, edit the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</li><li>To locate your active configuration, view the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</li><li>To upgrade an existing installation, proceed to the <a href="@base-url/core/update.php">update script</a>.</li><li>View your <a href="@base-url">existing site</a>.</li></ul>', array('@base-url' => $base_url));
 }
 
 /**
diff --git a/core/includes/install.inc b/core/includes/install.inc
index f6848c9..256c79e 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -177,24 +177,20 @@ function drupal_get_database_types() {
  *   and comment properties.
  *   @code
  *   $settings['config_directories'] = array(
- *     CONFIG_ACTIVE_DIRECTORY => array(
- *       'path' => (object) array(
- *         'value' => 'config__hash/active'
- *         'required' => TRUE,
- *       ),
+ *     CONFIG_ACTIVE_DIRECTORY => (object) array(
+ *       'value' => 'config_hash/active'
+ *       'required' => TRUE,
  *     ),
- *     CONFIG_STAGING_DIRECTORY => array(
- *       'path' => (object) array(
- *         'value' => 'config_hash/staging',
- *         'required' => TRUE,
- *       ),
+ *     CONFIG_STAGING_DIRECTORY => (object) array(
+ *       'value' => 'config_hash/staging',
+ *       'required' => TRUE,
  *     ),
  *   );
  *   @endcode
  *   gets dumped as:
  *   @code
- *   $config_directories['active']['path'] = 'config__hash/active';
- *   $config_directories['staging']['path'] = 'config__hash/staging'
+ *   $config_directories['active'] = 'config_hash/active';
+ *   $config_directories['staging'] = 'config_hash/staging'
  *   @endcode
  */
 function drupal_rewrite_settings($settings = array(), $settings_file = NULL) {
@@ -452,17 +448,13 @@ function drupal_install_config_directories($mode = NULL) {
   if (empty($config_directories)) {
     $config_directories_hash = Crypt::randomStringHashed(55);
     $settings['config_directories'] = array(
-      CONFIG_ACTIVE_DIRECTORY => array(
-        'path' => (object) array(
-          'value' => 'config_' . $config_directories_hash . '/active',
-          'required' => TRUE,
-        ),
+      CONFIG_ACTIVE_DIRECTORY => (object) array(
+        'value' => conf_path() . '/files/config_' . $config_directories_hash . '/active',
+        'required' => TRUE,
       ),
-      CONFIG_STAGING_DIRECTORY => array(
-        'path' => (object) array(
-          'value' => 'config_' . $config_directories_hash . '/staging',
-          'required' => TRUE,
-        ),
+      CONFIG_STAGING_DIRECTORY => (object) array(
+        'value' => conf_path() . '/files/config_' . $config_directories_hash . '/staging',
+        'required' => TRUE,
       ),
     );
     // Rewrite settings.php, which also sets the value as global variable.
diff --git a/core/includes/mail.inc b/core/includes/mail.inc
index dfa3f1d..61dfb7e 100644
--- a/core/includes/mail.inc
+++ b/core/includes/mail.inc
@@ -96,8 +96,8 @@
  *   Language code to use to compose the e-mail.
  * @param array $params
  *   (optional) Parameters to build the e-mail.
- * @param string|null $from
- *   Sets From to this value, if given.
+ * @param string|null $reply
+ *   Optional e-mail address to be used to answer.
  * @param bool $send
  *   If TRUE, drupal_mail() will call drupal_mail_system()->mail() to deliver
  *   the message, and store the result in $message['result']. Modules
@@ -111,13 +111,12 @@
  *   written to the watchdog. (Success means nothing more than the message being
  *   accepted at php-level, which still doesn't guarantee it to be delivered.)
  */
-function drupal_mail($module, $key, $to, $langcode, $params = array(), $from = NULL, $send = TRUE) {
+function drupal_mail($module, $key, $to, $langcode, $params = array(), $reply = NULL, $send = TRUE) {
   $site_config = \Drupal::config('system.site');
   $site_mail = $site_config->get('mail');
   if (empty($site_mail)) {
     $site_mail = ini_get('sendmail_from');
   }
-  $default_from = $site_mail;
 
   // Bundle up the variables into a structured array for altering.
   $message = array(
@@ -125,7 +124,8 @@ function drupal_mail($module, $key, $to, $langcode, $params = array(), $from = N
     'module'   => $module,
     'key'      => $key,
     'to'       => $to,
-    'from'     => isset($from) ? $from : $default_from,
+    'from'     => $site_mail,
+    'reply-to' => $reply,
     'langcode' => $langcode,
     'params'   => $params,
     'send'     => TRUE,
@@ -140,15 +140,13 @@ function drupal_mail($module, $key, $to, $langcode, $params = array(), $from = N
     'Content-Transfer-Encoding' => '8Bit',
     'X-Mailer'                  => 'Drupal'
   );
-  if ($default_from) {
-    // To prevent e-mail from looking like spam, the addresses in the Sender and
-    // Return-Path headers should have a domain authorized to use the originating
-    // SMTP server.
-    $headers['Sender'] = $headers['Return-Path'] = $default_from;
-    $headers['From'] = $site_config->get('name') . ' <' . $default_from . '>';
-  }
-  if ($from && $from != $default_from) {
-    $headers['From'] = $from;
+  // To prevent e-mail from looking like spam, the addresses in the Sender and
+  // Return-Path headers should have a domain authorized to use the
+  // originating SMTP server.
+  $headers['Sender'] = $headers['Return-Path'] = $site_mail;
+  $headers['From'] = $site_config->get('name') . ' <' . $site_mail . '>';
+  if ($reply) {
+    $headers['Reply-to'] = $reply;
   }
   $message['headers'] = $headers;
 
@@ -181,7 +179,7 @@ function drupal_mail($module, $key, $to, $langcode, $params = array(), $from = N
       $message['result'] = $system->mail($message);
       // Log errors.
       if (!$message['result']) {
-        watchdog('mail', 'Error sending e-mail (from %from to %to).', array('%from' => $message['from'], '%to' => $message['to']), WATCHDOG_ERROR);
+        watchdog('mail', 'Error sending e-mail (from %from to %to with reply-to %reply).', array('%from' => $message['from'], '%to' => $message['to'], '%reply' => $message['reply-to'] ? $message['reply-to'] : t('not set')), WATCHDOG_ERROR);
         drupal_set_message(t('Unable to send e-mail. Contact the site administrator if the problem persists.'), 'error');
       }
     }
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 9545be7..3db84a9 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -2054,15 +2054,22 @@ function _template_preprocess_default_variables() {
  * @see system_elements()
  */
 function template_preprocess_html(&$variables) {
-  $language_interface = language(Language::TYPE_INTERFACE);
+  /** @var $page \Drupal\Core\Page\HtmlPage */
+  $page = $variables['page_object'];
+
+  $variables['html_attributes'] = $page->getHtmlAttributes();
+  $variables['attributes'] = $page->getBodyAttributes();
+  $variables['page'] = $page->getContent();
 
   // Compile a list of classes that are going to be applied to the body element.
   // This allows advanced theming based on context (home page, node of certain type, etc.).
-  $variables['attributes']['class'][] = 'html';
+  $body_classes = $variables['attributes']['class'];
+  $body_classes[] = 'html';
   // Add a class that tells us whether we're on the front page or not.
-  $variables['attributes']['class'][] = $variables['is_front'] ? 'front' : 'not-front';
+  $body_classes[] = $variables['is_front'] ? 'front' : 'not-front';
   // Add a class that tells us whether the page is viewed by an authenticated user or not.
-  $variables['attributes']['class'][] = $variables['logged_in'] ? 'logged-in' : 'not-logged-in';
+  $body_classes[] = $variables['logged_in'] ? 'logged-in' : 'not-logged-in';
+  $variables['attributes']['class'] = $body_classes;
 
   // Populate the body classes.
   if ($suggestions = theme_get_suggestions(arg(), 'page', '-')) {
@@ -2080,6 +2087,7 @@ function template_preprocess_html(&$variables) {
   $variables['html_attributes'] = new Attribute;
 
   // HTML element attributes.
+  $language_interface = \Drupal::service('language_manager')->getLanguage();
   $variables['html_attributes']['lang'] = $language_interface->id;
   $variables['html_attributes']['dir'] = $language_interface->direction ? 'rtl' : 'ltr';
 
@@ -2097,9 +2105,9 @@ function template_preprocess_html(&$variables) {
 
   $site_config = \Drupal::config('system.site');
   // Construct page title.
-  if (!empty($variables['page']['#title'])) {
+  if ($page->hasTitle()) {
     $head_title = array(
-      'title' => strip_tags($variables['page']['#title']),
+      'title' => strip_tags($page->getTitle()),
       'name' => String::checkPlain($site_config->get('name')),
     );
   }
@@ -2149,15 +2157,8 @@ function template_preprocess_html(&$variables) {
 
   drupal_add_library('system', 'html5shiv', TRUE);
 
-  // Render page_top and page_bottom into top level variables.
-  $variables['page_top'] = array();
-  if (isset($variables['page']['page_top'])) {
-    $variables['page_top'] = drupal_render($variables['page']['page_top']);
-  }
-  $variables['page_bottom'] = array();
-  if (isset($variables['page']['page_bottom'])) {
-    $variables['page_bottom'][]['#markup'] = drupal_render($variables['page']['page_bottom']);
-  }
+  $variables['page_top'][] = array('#markup' => $page->getBodyTop());
+  $variables['page_bottom'][] = array('#markup' => $page->getBodyBottom());
 
   // Add footer scripts as '#markup' so they can be rendered with other
   // elements in page_bottom.
@@ -2557,7 +2558,7 @@ function drupal_common_theme() {
   return array(
     // From theme.inc.
     'html' => array(
-      'render element' => 'page',
+      'variables' => array('page_object' => NULL),
       'template' => 'html',
     ),
     'page' => array(
@@ -2631,7 +2632,7 @@ function drupal_common_theme() {
     ),
     // From theme.maintenance.inc.
     'maintenance_page' => array(
-      'variables' => array('content' => NULL, 'show_messages' => TRUE),
+      'variables' => array('content' => NULL, 'show_messages' => TRUE, 'page' => array()),
       'template' => 'maintenance-page',
     ),
     'install_page' => array(
diff --git a/core/includes/update.inc b/core/includes/update.inc
index b63bf8c..73f9b7f 100644
--- a/core/includes/update.inc
+++ b/core/includes/update.inc
@@ -1643,7 +1643,7 @@ function update_language_list($flags = Language::STATE_CONFIGURABLE) {
     if (language_multilingual() || \Drupal::moduleHandler()->moduleExists('language')) {
       // Use language module configuration if available. We can not use
       // entity_load_multiple() because this breaks during updates.
-      $language_entities = config_get_storage_names_with_prefix('language.entity');
+      $language_entities = config_get_storage_names_with_prefix('language.entity.');
 
       // Initialize default property so callers have an easy reference and can
       // save the same object without data loss.
diff --git a/core/includes/utility.inc b/core/includes/utility.inc
index a900262..023ecb0 100644
--- a/core/includes/utility.inc
+++ b/core/includes/utility.inc
@@ -34,6 +34,12 @@ function drupal_var_export($var, $prefix = '') {
  * @see rebuild.php
  */
 function drupal_rebuild() {
+  // Remove Drupal's error and exception handlers; they rely on a working
+  // service container and other subsystems and will only cause a fatal error
+  // that hides the actual error.
+  restore_error_handler();
+  restore_exception_handler();
+
   // drupal_bootstrap(DRUPAL_BOOTSTRAP_KERNEL) will build a new kernel. This
   // comes before DRUPAL_BOOTSTRAP_PAGE_CACHE.
   PhpStorageFactory::get('service_container')->deleteAll();
@@ -50,4 +56,9 @@ function drupal_rebuild() {
 
   drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
   drupal_flush_all_caches();
+
+  // Restore Drupal's error and exception handlers.
+  // @see _drupal_bootstrap_configuration()
+  set_error_handler('_drupal_error_handler');
+  set_exception_handler('_drupal_exception_handler');
 }
diff --git a/core/lib/Drupal/Component/Annotation/AnnotationBase.php b/core/lib/Drupal/Component/Annotation/AnnotationBase.php
new file mode 100644
index 0000000..f256d42
--- /dev/null
+++ b/core/lib/Drupal/Component/Annotation/AnnotationBase.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Annotation\AnnotationBase.
+ */
+
+namespace Drupal\Component\Annotation;
+
+/**
+ * Provides a base class for classed annotations.
+ */
+abstract class AnnotationBase implements AnnotationInterface {
+
+  /**
+   * The annotated class ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The class used for this annotated class.
+   *
+   * @var string
+   */
+  protected $class;
+
+  /**
+   * The provider of the annotated class.
+   *
+   * @var string
+   */
+  protected $provider;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getProvider() {
+    return $this->provider;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setProvider($provider) {
+    $this->provider = $provider;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getId() {
+    return $this->id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getClass() {
+    return $this->class;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setClass($class) {
+    $this->class = $class;
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Annotation/AnnotationInterface.php b/core/lib/Drupal/Component/Annotation/AnnotationInterface.php
index e3aacc6..03caad8 100644
--- a/core/lib/Drupal/Component/Annotation/AnnotationInterface.php
+++ b/core/lib/Drupal/Component/Annotation/AnnotationInterface.php
@@ -17,4 +17,39 @@
    */
   public function get();
 
+  /**
+   * Returns the name of the provider of the annotated class.
+   *
+   * @return string
+   */
+  public function getProvider();
+
+  /**
+   * Sets the name of the provider of the annotated class.
+   *
+   * @param string $provider
+   */
+  public function setProvider($provider);
+
+  /**
+   * Returns the unique ID for this annotated class.
+   *
+   * @return string
+   */
+  public function getId();
+
+  /**
+   * Returns the class of the annotated class.
+   *
+   * @return string
+   */
+  public function getClass();
+
+  /**
+   * Sets the class of the annotated class.
+   *
+   * @param string $class
+   */
+  public function setClass($class);
+
 }
diff --git a/core/lib/Drupal/Component/Annotation/Plugin.php b/core/lib/Drupal/Component/Annotation/Plugin.php
index b67092b..115e94b 100644
--- a/core/lib/Drupal/Component/Annotation/Plugin.php
+++ b/core/lib/Drupal/Component/Annotation/Plugin.php
@@ -71,10 +71,45 @@ protected function parse(array $values) {
   }
 
   /**
-   * Implements Drupal\Core\Annotation\AnnotationInterface::get().
+   * {@inheritdoc}
    */
   public function get() {
     return $this->definition;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getProvider() {
+    return isset($this->definition['provider']) ? $this->definition['provider'] : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setProvider($provider) {
+    $this->definition['provider'] = $provider;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getId() {
+    return $this->definition['id'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getClass() {
+    return $this->definition['class'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setClass($class) {
+    $this->definition['class'] = $class;
+  }
+
 }
diff --git a/core/lib/Drupal/Component/Annotation/Plugin/Discovery/AnnotatedClassDiscovery.php b/core/lib/Drupal/Component/Annotation/Plugin/Discovery/AnnotatedClassDiscovery.php
index b9236be..b04e39d 100644
--- a/core/lib/Drupal/Component/Annotation/Plugin/Discovery/AnnotatedClassDiscovery.php
+++ b/core/lib/Drupal/Component/Annotation/Plugin/Discovery/AnnotatedClassDiscovery.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Component\Annotation\Plugin\Discovery;
 
+use Drupal\Component\Annotation\AnnotationInterface;
 use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
 use Drupal\Component\Annotation\Reflection\MockFileFinder;
 use Doctrine\Common\Annotations\SimpleAnnotationReader;
@@ -115,12 +116,12 @@ public function getDefinitions() {
               $finder = MockFileFinder::create($fileinfo->getPathName());
               $parser = new StaticReflectionParser($class, $finder, TRUE);
 
+              /** @var $annotation \Drupal\Component\Annotation\AnnotationInterface */
               if ($annotation = $reader->getClassAnnotation($parser->getReflectionClass(), $this->pluginDefinitionAnnotationName)) {
+                $this->prepareAnnotationDefinition($annotation, $class);
                 // AnnotationInterface::get() returns the array definition
                 // instead of requiring us to work with the annotation object.
-                $definition = $annotation->get();
-                $definition['class'] = $class;
-                $definitions[$definition['id']] = $definition;
+                $definitions[$annotation->getId()] = $annotation->get();
               }
             }
           }
@@ -135,6 +136,18 @@ public function getDefinitions() {
   }
 
   /**
+   * Prepares the annotation definition.
+   *
+   * @param \Drupal\Component\Annotation\AnnotationInterface $annotation
+   *   The annotation derived from the plugin.
+   * @param string $class
+   *   The class used for the plugin.
+   */
+  protected function prepareAnnotationDefinition(AnnotationInterface $annotation, $class) {
+    $annotation->setClass($class);
+  }
+
+  /**
    * Returns an array of PSR-0 namespaces to search for plugin classes.
    */
   protected function getPluginNamespaces() {
diff --git a/core/lib/Drupal/Component/Annotation/PluginID.php b/core/lib/Drupal/Component/Annotation/PluginID.php
index dcb27aa..fe8e599 100644
--- a/core/lib/Drupal/Component/Annotation/PluginID.php
+++ b/core/lib/Drupal/Component/Annotation/PluginID.php
@@ -12,7 +12,7 @@
  *
  * @Annotation
  */
-class PluginID implements AnnotationInterface {
+class PluginID extends AnnotationBase {
 
   /**
    * The plugin ID.
@@ -29,7 +29,16 @@ class PluginID implements AnnotationInterface {
   public function get() {
     return array(
       'id' => $this->value,
+      'class' => $this->class,
+      'provider' => $this->provider,
     );
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getId() {
+    return $this->value;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Access/AccessException.php b/core/lib/Drupal/Core/Access/AccessException.php
index a44738e..4f21092 100644
--- a/core/lib/Drupal/Core/Access/AccessException.php
+++ b/core/lib/Drupal/Core/Access/AccessException.php
@@ -8,7 +8,10 @@
 namespace Drupal\Core\Access;
 
 /**
- * An exception thrown for invalid access callback return values.
+ * An exception thrown for access errors.
+ *
+ * Examples could be invalid access callback return values, or invalid access
+ * objects being used.
  */
 class AccessException extends \RuntimeException {
 }
diff --git a/core/lib/Drupal/Core/Access/AccessManager.php b/core/lib/Drupal/Core/Access/AccessManager.php
index f33d47f..89e9268 100644
--- a/core/lib/Drupal/Core/Access/AccessManager.php
+++ b/core/lib/Drupal/Core/Access/AccessManager.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Access;
 
 use Drupal\Core\ParamConverter\ParamConverterManager;
+use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
 use Drupal\Core\Routing\RequestHelper;
 use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -16,7 +17,6 @@
 use Symfony\Component\Routing\Route;
 use Symfony\Component\DependencyInjection\ContainerAware;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Symfony\Component\Routing\Exception\RouteNotFoundException;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
@@ -118,9 +118,15 @@ public function setRequest(Request $request) {
    *
    * @param string $service_id
    *   The ID of the service in the Container that provides a check.
+   * @param array $applies_checks
+   *   (optional) An array of route requirement keys the checker service applies
+   *   to.
    */
-  public function addCheckService($service_id) {
+  public function addCheckService($service_id, array $applies_checks = array()) {
     $this->checkIds[] = $service_id;
+    foreach ($applies_checks as $applies_check) {
+      $this->staticRequirementMap[$applies_check][] = $service_id;
+    }
   }
 
   /**
@@ -130,7 +136,7 @@ public function addCheckService($service_id) {
    *   A collection of routes to apply checks to.
    */
   public function setChecks(RouteCollection $routes) {
-    $this->loadAccessRequirementMap();
+    $this->loadDynamicRequirementMap();
     foreach ($routes as $route) {
       if ($checks = $this->applies($route)) {
         $route->setOption('_access_checks', $checks);
@@ -329,19 +335,24 @@ protected function loadCheck($service_id) {
       throw new \InvalidArgumentException(sprintf('No check has been registered for %s', $service_id));
     }
 
-    $this->checks[$service_id] = $this->container->get($service_id);
+    $check = $this->container->get($service_id);
+
+    if (!($check instanceof RoutingAccessInterface)) {
+      throw new AccessException('All access checks must implement AccessInterface.');
+    }
+
+    $this->checks[$service_id] = $check;
   }
 
   /**
    * Compiles a mapping of requirement keys to access checker service IDs.
    */
-  public function loadAccessRequirementMap() {
-    if (isset($this->staticRequirementMap, $this->dynamicRequirementMap)) {
+  public function loadDynamicRequirementMap() {
+    if (isset($this->dynamicRequirementMap)) {
       return;
     }
 
     // Set them here, so we can use the isset() check above.
-    $this->staticRequirementMap = array();
     $this->dynamicRequirementMap = array();
 
     foreach ($this->checkIds as $service_id) {
@@ -349,14 +360,8 @@ public function loadAccessRequirementMap() {
         $this->loadCheck($service_id);
       }
 
-      // Empty arrays will not register anything.
-      if (is_subclass_of($this->checks[$service_id], 'Drupal\Core\Access\StaticAccessCheckInterface')) {
-        foreach ((array) $this->checks[$service_id]->appliesTo() as $key) {
-          $this->staticRequirementMap[$key][] = $service_id;
-        }
-      }
-      // Add the service ID to a the regular that will be iterated over.
-      else {
+      // Add the service ID to an array that will be iterated over.
+      if ($this->checks[$service_id] instanceof AccessCheckInterface) {
         $this->dynamicRequirementMap[] = $service_id;
       }
     }
diff --git a/core/lib/Drupal/Core/Access/CsrfAccessCheck.php b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
index 98be68c..9744d6c 100644
--- a/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
+++ b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
@@ -7,8 +7,8 @@
 
 namespace Drupal\Core\Access;
 
-use Drupal\Core\Access\CsrfTokenGenerator;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -19,7 +19,7 @@
  * a token generated by \Drupal::csrfToken()->get() using the same value as the
  * "_csrf_token" parameter in the route.
  */
-class CsrfAccessCheck implements StaticAccessCheckInterface {
+class CsrfAccessCheck implements RoutingAccessInterface {
 
   /**
    * The CSRF token generator.
@@ -41,13 +41,6 @@ function __construct(CsrfTokenGenerator $csrf_token) {
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    return array('_csrf_token');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     // If this is the controller request, check CSRF access as normal.
     if ($request->attributes->get('_controller_request')) {
diff --git a/core/lib/Drupal/Core/Access/CustomAccessCheck.php b/core/lib/Drupal/Core/Access/CustomAccessCheck.php
index 20f0ead..c116e66 100644
--- a/core/lib/Drupal/Core/Access/CustomAccessCheck.php
+++ b/core/lib/Drupal/Core/Access/CustomAccessCheck.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Access;
 
 use Drupal\Core\Controller\ControllerResolverInterface;
+use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
@@ -22,7 +23,7 @@
  * cannot reuse any stored property of your actual controller instance used
  * to generate the output.
  */
-class CustomAccessCheck implements StaticAccessCheckInterface {
+class CustomAccessCheck implements RoutingAccessInterface {
 
   /**
    * The controller resolver.
@@ -44,13 +45,6 @@ public function __construct(ControllerResolverInterface $controller_resolver) {
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    return array('_custom_access');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     $access_controller = $route->getRequirement('_custom_access');
 
diff --git a/core/lib/Drupal/Core/Access/DefaultAccessCheck.php b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php
index 123fc5d..dc26067 100644
--- a/core/lib/Drupal/Core/Access/DefaultAccessCheck.php
+++ b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php
@@ -8,20 +8,14 @@
 namespace Drupal\Core\Access;
 
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Allows access to routes to be controlled by an '_access' boolean parameter.
  */
-class DefaultAccessCheck implements StaticAccessCheckInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function appliesTo() {
-    return array('_access');
-  }
+class DefaultAccessCheck implements RoutingAccessInterface {
 
   /**
    * {@inheritdoc}
diff --git a/core/lib/Drupal/Core/Access/StaticAccessCheckInterface.php b/core/lib/Drupal/Core/Access/StaticAccessCheckInterface.php
deleted file mode 100644
index c01b719..0000000
--- a/core/lib/Drupal/Core/Access/StaticAccessCheckInterface.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Access\StaticAccessCheckInterface.
- */
-
-namespace Drupal\Core\Access;
-
-use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
-
-/**
- * An access check service determines access rules for particular routes.
- *
- * This interface is specifically for routes that know exactly which requirement
- * keys they should react to for a route.
- */
-interface StaticAccessCheckInterface extends RoutingAccessInterface {
-
-  /**
-   * Declares the route requirement keys this access checker applies to.
-   *
-   * This should be used when the requirement matching for a route is static,
-   * and does not require any further information. For example, '_access' will
-   * provide TRUE, or FALSE. We do not need any more information other than the
-   * route provides this requirement key.
-   *
-   * @return array
-   *   An array of route requirement keys this access checker applies to. An
-   *   empty array will check all routes using the apply method.
-   */
-  public function appliesTo();
-
-}
diff --git a/core/lib/Drupal/Core/Annotation/Translation.php b/core/lib/Drupal/Core/Annotation/Translation.php
index 96ffa9c..ebb2655 100644
--- a/core/lib/Drupal/Core/Annotation/Translation.php
+++ b/core/lib/Drupal/Core/Annotation/Translation.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Annotation;
 
-use Drupal\Component\Annotation\AnnotationInterface;
+use Drupal\Component\Annotation\AnnotationBase;
 
 /**
  * @defgroup plugin_translatable Translatable plugin metadata
@@ -45,7 +45,7 @@
  *
  * @ingroup plugin_translatable
  */
-class Translation implements AnnotationInterface {
+class Translation extends AnnotationBase {
 
   /**
    * The translation of the value passed to the constructor of the class.
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
index b363cc4..979dddd 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
@@ -23,7 +23,7 @@ public function load() {
 
     // Sort the entities using the entity class's sort() method.
     // See \Drupal\Core\Config\Entity\ConfigEntityBase::sort().
-    uasort($entities, array($this->entityInfo['class'], 'sort'));
+    uasort($entities, array($this->entityInfo->getClass(), 'sort'));
     return $entities;
   }
 
@@ -41,7 +41,7 @@ public function getOperations(EntityInterface $entity) {
       $operations['edit']['href'] = $uri['path'];
     }
 
-    if (isset($this->entityInfo['entity_keys']['status'])) {
+    if ($this->entityInfo->hasKey('status')) {
       if (!$entity->status()) {
         $operations['enable'] = array(
           'title' => t('Enable'),
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
index 0ee55e2..458d765 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Config\Config;
 use Drupal\Core\Config\ConfigFactory;
 use Drupal\Core\Config\StorageInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Component\Uuid\UuidInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -79,10 +80,8 @@ class ConfigStorageController extends EntityStorageControllerBase {
   /**
    * Constructs a ConfigStorageController object.
    *
-   * @param string $entity_type
-   *   The entity type for which the instance is created.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Config\ConfigFactory $config_factory
    *   The config factory service.
    * @param \Drupal\Core\Config\StorageInterface $config_storage
@@ -92,17 +91,11 @@ class ConfigStorageController extends EntityStorageControllerBase {
    * @param \Drupal\Component\Uuid\UuidInterface $uuid_service
    *   The UUID service.
    */
-  public function __construct($entity_type, array $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, QueryFactory $entity_query_factory, UuidInterface $uuid_service) {
-    parent::__construct($entity_type, $entity_info);
+  public function __construct(EntityTypeInterface $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, QueryFactory $entity_query_factory, UuidInterface $uuid_service) {
+    parent::__construct($entity_info);
 
-    $this->idKey = $this->entityInfo['entity_keys']['id'];
-
-    if (isset($this->entityInfo['entity_keys']['status'])) {
-      $this->statusKey = $this->entityInfo['entity_keys']['status'];
-    }
-    else {
-      $this->statusKey = FALSE;
-    }
+    $this->idKey = $this->entityInfo->getKey('id');
+    $this->statusKey = $this->entityInfo->getKey('status');
 
     $this->configFactory = $config_factory;
     $this->configStorage = $config_storage;
@@ -113,9 +106,8 @@ public function __construct($entity_type, array $entity_info, ConfigFactory $con
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
       $container->get('config.factory'),
       $container->get('config.storage'),
@@ -208,7 +200,7 @@ public function getQuery($conjunction = 'AND') {
    *   The full configuration prefix, for example 'views.view.'.
    */
   public function getConfigPrefix() {
-    return $this->entityInfo['config_prefix'] . '.';
+    return $this->entityInfo->getConfigPrefix() . '.';
   }
 
   /**
@@ -249,7 +241,7 @@ public static function getIDFromConfigName($config_name, $config_prefix) {
    *   A SelectQuery object for loading the entity.
    */
   protected function buildQuery($ids, $revision_id = FALSE) {
-    $config_class = $this->entityInfo['class'];
+    $config_class = $this->entityInfo->getClass();
     $prefix = $this->getConfigPrefix();
 
     // Get the names of the configuration entities we are going to load.
@@ -276,7 +268,7 @@ protected function buildQuery($ids, $revision_id = FALSE) {
    * Implements Drupal\Core\Entity\EntityStorageControllerInterface::create().
    */
   public function create(array $values) {
-    $class = $this->entityInfo['class'];
+    $class = $this->entityInfo->getClass();
     $class::preCreate($this, $values);
 
     // Set default language to site default if not provided.
@@ -314,7 +306,7 @@ public function delete(array $entities) {
       return;
     }
 
-    $entity_class = $this->entityInfo['class'];
+    $entity_class = $this->entityInfo->getClass();
     $entity_class::preDelete($this, $entities);
     foreach ($entities as $entity) {
       $this->invokeHook('predelete', $entity);
@@ -458,7 +450,7 @@ public function importCreate($name, Config $new_config, Config $old_config) {
    *   A configuration object containing the old configuration data.
    */
   public function importUpdate($name, Config $new_config, Config $old_config) {
-    $id = static::getIDFromConfigName($name, $this->entityInfo['config_prefix']);
+    $id = static::getIDFromConfigName($name, $this->entityInfo->getConfigPrefix());
     $entity = $this->load($id);
     $entity->setSyncing(TRUE);
     $entity->original = clone $entity;
@@ -489,7 +481,7 @@ public function importUpdate($name, Config $new_config, Config $old_config) {
    *   A configuration object containing the old configuration data.
    */
   public function importDelete($name, Config $new_config, Config $old_config) {
-    $id = static::getIDFromConfigName($name, $this->entityInfo['config_prefix']);
+    $id = static::getIDFromConfigName($name, $this->entityInfo->getConfigPrefix());
     $entity = $this->load($id);
     $entity->setSyncing(TRUE);
     $entity->delete();
diff --git a/core/lib/Drupal/Core/Config/Entity/DraggableListController.php b/core/lib/Drupal/Core/Config/Entity/DraggableListController.php
index 2cb3f43..8e61eb4 100644
--- a/core/lib/Drupal/Core/Config/Entity/DraggableListController.php
+++ b/core/lib/Drupal/Core/Config/Entity/DraggableListController.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\FormInterface;
 
@@ -41,12 +42,12 @@
   /**
    * {@inheritdoc}
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler) {
+    parent::__construct($entity_info, $storage, $module_handler);
 
     // Check if the entity type supports weighting.
-    if (!empty($this->entityInfo['entity_keys']['weight'])) {
-      $this->weightKey = $this->entityInfo['entity_keys']['weight'];
+    if ($this->entityInfo->hasKey('weight')) {
+      $this->weightKey = $this->entityInfo->getKey('weight');
     }
   }
 
@@ -99,7 +100,7 @@ public function buildForm(array $form, array &$form_state) {
     $form[$this->entitiesKey] = array(
       '#type' => 'table',
       '#header' => $this->buildHeader(),
-      '#empty' => t('There is no @label yet.', array('@label' => $this->entityInfo['label'])),
+      '#empty' => t('There is no @label yet.', array('@label' => $this->entityInfo->getLabel())),
       '#tabledrag' => array(
         array(
           'action' => 'order',
diff --git a/core/lib/Drupal/Core/Config/Entity/Query/Query.php b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
index b398f83..130f171 100644
--- a/core/lib/Drupal/Core/Config/Entity/Query/Query.php
+++ b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
@@ -86,7 +86,7 @@ public function condition($property, $value = NULL, $operator = NULL, $langcode
   public function execute() {
     // Load all config files.
     $entity_info = $this->entityManager->getDefinition($this->getEntityType());
-    $prefix = $entity_info['config_prefix'] . '.';
+    $prefix = $entity_info->getConfigPrefix() . '.';
     $prefix_length = strlen($prefix);
     $names = $this->configStorage->listAll($prefix);
     $configs = array();
diff --git a/core/lib/Drupal/Core/Controller/AjaxController.php b/core/lib/Drupal/Core/Controller/AjaxController.php
index 7b2cece..2f8498e 100644
--- a/core/lib/Drupal/Core/Controller/AjaxController.php
+++ b/core/lib/Drupal/Core/Controller/AjaxController.php
@@ -10,8 +10,11 @@
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\InsertCommand;
 use Drupal\Core\Ajax\PrependCommand;
+use Drupal\Core\Page\HtmlFragment;
+use Drupal\Core\Page\HtmlPage;
 use Symfony\Component\DependencyInjection\ContainerAware;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Default controller for ajax requests.
@@ -19,6 +22,23 @@
 class AjaxController extends ContainerAware {
 
   /**
+   * The controller resolver.
+   *
+   * @var \Drupal\Core\Controller\ControllerResolverInterface
+   */
+  protected $controllerResolver;
+
+  /**
+   * Constructs a new AjaxController instance.
+   *
+   * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
+   *   The controller resolver.
+   */
+  public function __construct(ControllerResolverInterface $controller_resolver) {
+    $this->controllerResolver = $controller_resolver;
+  }
+
+  /**
    * Controller method for AJAX content.
    *
    * @param \Symfony\Component\HttpFoundation\Request $request
@@ -30,50 +50,66 @@ class AjaxController extends ContainerAware {
    *   A response object.
    */
   public function content(Request $request, $_content) {
+    $content = $this->getContentResult($request, $_content);
+    // If there is already an AjaxResponse, then return it without
+    // manipulation.
+    if ($content instanceof AjaxResponse && $content->isOk()) {
+      return $content;
+    }
 
-    // @todo When we have a Generator, we can replace the forward() call with
-    // a render() call, which would handle ESI and hInclude as well.  That will
-    // require an _internal route.  For examples, see:
-    // https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing/internal.xml
-    // https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/InternalController.php
-    $attributes = clone $request->attributes;
-    $controller = $_content;
+    // Allow controllers to return a HtmlFragment or a Response object directly.
+    if ($content instanceof HtmlFragment) {
+      $content = $content->getContent();
+    }
+    if ($content instanceof Response) {
+      $content = $content->getContent();
+    }
 
-    // We need to clean up the derived information and such so that the
-    // subrequest can be processed properly without leaking data through.
-    $attributes->remove('_system_path');
-    $attributes->remove('_content');
-    $attributes->remove('_legacy');
+    // Most controllers return a render array, but some return a string.
+    if (!is_array($content)) {
+      $content = array(
+        '#markup' => $content,
+      );
+    }
 
-    // Remove the accept header so the subrequest does not end up back in this
-    // controller.
-    $request->headers->remove('accept');
-    // Remove the header in order to let the subrequest not think that it's an
-    // ajax request, see \Drupal\Core\ContentNegotiation.
-    $request->headers->remove('x-requested-with');
+    $html = drupal_render($content);
 
-    $response = $this->container->get('http_kernel')->forward($controller, $attributes->all(), $request->query->all());
-    // For successful (HTTP status 200) responses.
-    if ($response->isOk()) {
-      // If there is already an AjaxResponse, then return it without
-      // manipulation.
-      if (!($response instanceof AjaxResponse)) {
-        // Pull the content out of the response.
-        $content = $response->getContent();
-        // A page callback could return a render array or a string.
-        $html = is_string($content) ? $content : drupal_render($content);
-        $response = new AjaxResponse();
-        // The selector for the insert command is NULL as the new content will
-        // replace the element making the ajax call. The default 'replaceWith'
-        // behavior can be changed with #ajax['method'].
-        $response->addCommand(new InsertCommand(NULL, $html));
-        $status_messages = array('#theme' => 'status_messages');
-        $output = drupal_render($status_messages);
-        if (!empty($output)) {
-          $response->addCommand(new PrependCommand(NULL, $output));
-        }
-      }
+    $response = new AjaxResponse();
+    // The selector for the insert command is NULL as the new content will
+    // replace the element making the ajax call. The default 'replaceWith'
+    // behavior can be changed with #ajax['method'].
+    $response->addCommand(new InsertCommand(NULL, $html));
+    $status_messages = array('#theme' => 'status_messages');
+    $output = drupal_render($status_messages);
+    if (!empty($output)) {
+      $response->addCommand(new PrependCommand(NULL, $output));
     }
     return $response;
   }
+
+  /**
+   * Returns the result of invoking the sub-controller.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   * @param mixed $controller_definition
+   *   A controller definition string, or a callable object/closure.
+   *
+   * @return mixed
+   *   The result of invoking the controller. Render arrays, strings, HtmlPage,
+   *   and HtmlFragment objects are possible.
+   */
+  public function getContentResult(Request $request, $controller_definition) {
+    if ($controller_definition instanceof \Closure) {
+      $callable = $controller_definition;
+    }
+    else {
+      $callable = $this->controllerResolver->getControllerFromDefinition($controller_definition);
+    }
+    $arguments = $this->controllerResolver->getArguments($request, $callable);
+    $page_content = call_user_func_array($callable, $arguments);
+
+    return $page_content;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Controller/DialogController.php b/core/lib/Drupal/Core/Controller/DialogController.php
index 7bb34ff..5fda730 100644
--- a/core/lib/Drupal/Core/Controller/DialogController.php
+++ b/core/lib/Drupal/Core/Controller/DialogController.php
@@ -9,9 +9,10 @@
 
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\OpenDialogCommand;
+use Drupal\Core\Page\HtmlPage;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Defines a default controller for dialog requests.
@@ -19,11 +20,11 @@
 class DialogController {
 
   /**
-   * The HttpKernel object to use for subrequests.
+   * The controller resolver service.
    *
-   * @var \Symfony\Component\HttpKernel\HttpKernelInterface
+   * @var \Drupal\Core\Controller\ControllerResolverInterface
    */
-  protected $httpKernel;
+  protected $controllerResolver;
 
   /**
    * The title resolver.
@@ -35,112 +36,126 @@ class DialogController {
   /**
    * Constructs a new DialogController.
    *
-   * @param \Symfony\Component\HttpKernel\HttpKernelInterface $kernel
-   *   The kernel.
+   * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
+   *   The controller resolver service.
    * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
    *   The title resolver.
    */
-  public function __construct(HttpKernelInterface $kernel, TitleResolverInterface $title_resolver) {
-    $this->httpKernel = $kernel;
+  public function __construct(ControllerResolverInterface $controller_resolver, TitleResolverInterface $title_resolver) {
+    $this->controllerResolver = $controller_resolver;
     $this->titleResolver = $title_resolver;
   }
 
   /**
-   * Forwards request to a subrequest.
-   *
-   * @param \Symfony\Component\HttpFoundation\RequestRequest $request
-   *   The request object.
-   *
-   * @return \Symfony\Component\HttpFoundation\Response
-   *   A response object.
-   */
-  protected function forward(Request $request) {
-    // @todo When we have a Generator, we can replace the forward() call with
-    // a render() call, which would handle ESI and hInclude as well.  That will
-    // require an _internal route.  For examples, see:
-    // https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing/internal.xml
-    // https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/InternalController.php
-    $attributes = clone $request->attributes;
-    // We need to clean up the derived information and such so that the
-    // subrequest can be processed properly without leaking data through.
-    $attributes->remove('_system_path');
-    $attributes->set('dialog', TRUE);
-
-    // Remove the accept header so the subrequest does not end up back in this
-    // controller.
-    $request->headers->remove('accept');
-    // Remove the X-Requested-With header so the subrequest is not mistaken for
-    // an ajax request.
-    $request->headers->remove('x-requested-with');
-
-    return $this->httpKernel->forward(NULL, $attributes->all(), $request->query->all());
-  }
-
-  /**
    * Displays content in a modal dialog.
    *
-   * @param \Symfony\Component\HttpFoundation\RequestRequest $request
+   * @param \Symfony\Component\HttpFoundation\Request $request
    *   The request object.
+   * @param mixed $_content
+   *   A controller definition string, or a callable object/closure.
    *
    * @return \Drupal\Core\Ajax\AjaxResponse
    *   AjaxResponse to return the content wrapper in a modal dialog.
    */
-  public function modal(Request $request) {
-    return $this->dialog($request, TRUE);
+  public function modal(Request $request, $_content) {
+    return $this->dialog($request, $_content, TRUE);
   }
 
   /**
    * Displays content in a dialog.
    *
-   * @param \Symfony\Component\HttpFoundation\RequestRequest $request
+   * @param \Symfony\Component\HttpFoundation\Request $request
    *   The request object.
+   * @param mixed $_content
+   *   A controller definition string, or a callable object/closure.
    * @param bool $modal
    *   (optional) TRUE to render a modal dialog. Defaults to FALSE.
    *
    * @return \Drupal\Core\Ajax\AjaxResponse
    *   AjaxResponse to return the content wrapper in a dialog.
    */
-  public function dialog(Request $request, $modal = FALSE) {
-    $subrequest = $this->forward($request);
-    if ($subrequest->isOk()) {
-      $content = $subrequest->getContent();
-      if (!$title = $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT))) {
-        // @todo Remove use of drupal_get_title() when
-        //  http://drupal.org/node/1871596 is in.
-        $title = drupal_get_title();
-      }
-      $response = new AjaxResponse();
-      // Fetch any modal options passed in from data-dialog-options.
-      if (!($options = $request->request->get('dialogOptions'))) {
-        $options = array();
-      }
-      // Set modal flag and re-use the modal ID.
-      if ($modal) {
-        $options['modal'] = TRUE;
-        $target = '#drupal-modal';
+  public function dialog(Request $request, $_content, $modal = FALSE) {
+    $page_content = $this->getContentResult($request, $_content);
+
+    // Allow controllers to return a HtmlPage or a Response object directly.
+    if ($page_content instanceof HtmlPage) {
+      $page_content = $page_content->getContent();
+    }
+    if ($page_content instanceof Response) {
+      $page_content = $page_content->getContent();
+    }
+
+    // Most controllers return a render array, but some return a string.
+    if (!is_array($page_content)) {
+      $page_content = array(
+        '#markup' => $page_content,
+      );
+    }
+
+    $content = drupal_render($page_content);
+
+    // @todo Remove use of drupal_get_title() when
+    //  http://drupal.org/node/1871596 is in.
+    if (!$title = $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT))) {
+      // @todo Remove use of drupal_get_title() when
+      //  http://drupal.org/node/1871596 is in.
+      $title = drupal_get_title();
+    }
+    $response = new AjaxResponse();
+    // Fetch any modal options passed in from data-dialog-options.
+    if (!($options = $request->request->get('dialogOptions'))) {
+      $options = array();
+    }
+    // Set modal flag and re-use the modal ID.
+    if ($modal) {
+      $options['modal'] = TRUE;
+      $target = '#drupal-modal';
+    }
+    else {
+      // Generate the target wrapper for the dialog.
+      if (isset($options['target'])) {
+        // If the target was nominated in the incoming options, use that.
+        $target = $options['target'];
+        // Ensure the target includes the #.
+        if (substr($target, 0, 1) != '#') {
+          $target = '#' . $target;
+        }
+        // This shouldn't be passed on to jQuery.ui.dialog.
+        unset($options['target']);
       }
       else {
-        // Generate the target wrapper for the dialog.
-        if (isset($options['target'])) {
-          // If the target was nominated in the incoming options, use that.
-          $target = $options['target'];
-          // Ensure the target includes the #.
-          if (substr($target, 0, 1) != '#') {
-            $target = '#' . $target;
-          }
-          // This shouldn't be passed on to jQuery.ui.dialog.
-          unset($options['target']);
-        }
-        else {
-          // Generate a target based on the route id.
-          $route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME);
-          $target = '#' . drupal_html_id("drupal-dialog-$route_name");
-        }
+        // Generate a target based on the route id.
+        $route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME);
+        $target = '#' . drupal_html_id("drupal-dialog-$route_name");
       }
-      $response->addCommand(new OpenDialogCommand($target, $title, $content, $options));
-      return $response;
     }
-    // An error occurred in the subrequest, return that.
-    return $subrequest;
+    $response->addCommand(new OpenDialogCommand($target, $title, $content, $options));
+    return $response;
+  }
+
+  /**
+   * Returns the result of invoking the sub-controller.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   * @param mixed $controller_definition
+   *   A controller definition string, or a callable object/closure.
+   *
+   * @return mixed
+   *   The result of invoking the controller. Render arrays, strings, HtmlPage,
+   *   and HtmlFragment objects are possible.
+   */
+  public function getContentResult(Request $request, $controller_definition) {
+    if ($controller_definition instanceof \Closure) {
+      $callable = $controller_definition;
+    }
+    else {
+      $callable = $this->controllerResolver->getControllerFromDefinition($controller_definition);
+    }
+    $arguments = $this->controllerResolver->getArguments($request, $callable);
+    $page_content = call_user_func_array($callable, $arguments);
+
+    return $page_content;
   }
+
 }
diff --git a/core/lib/Drupal/Core/Controller/ExceptionController.php b/core/lib/Drupal/Core/Controller/ExceptionController.php
index 2ba6f2c..af66226 100644
--- a/core/lib/Drupal/Core/Controller/ExceptionController.php
+++ b/core/lib/Drupal/Core/Controller/ExceptionController.php
@@ -7,7 +7,10 @@
 
 namespace Drupal\Core\Controller;
 
-use Symfony\Component\DependencyInjection\ContainerAware;
+use Drupal\Core\Page\HtmlPageRendererInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\JsonResponse;
@@ -19,7 +22,7 @@
 /**
  * This controller handles HTTP errors generated by the routing system.
  */
-class ExceptionController extends ContainerAware {
+class ExceptionController extends HtmlControllerBase implements ContainerAwareInterface {
 
   /**
    * The content negotiation library.
@@ -29,14 +32,48 @@ class ExceptionController extends ContainerAware {
   protected $negotiation;
 
   /**
+   * The service container.
+   *
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
+   */
+  protected $container;
+
+  /**
+   * The page rendering service.
+   *
+   * @var \Drupal\Core\Page\HtmlPageRendererInterface
+   */
+  protected $renderer;
+
+  /**
    * Constructor.
    *
    * @param \Drupal\Core\ContentNegotiation $negotiation
    *   The content negotiation library to use to determine the correct response
    *   format.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
+   *   The translation manager.
+   * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
+   *   The title resolver.
+   * @param \Drupal\Core\Page\HtmlPageRendererInterface $renderer
+   *   The page renderer.
    */
-  public function __construct(ContentNegotiation $negotiation) {
+  public function __construct(ContentNegotiation $negotiation, TranslationInterface $translation_manager, TitleResolverInterface $title_resolver, HtmlPageRendererInterface $renderer) {
+    parent::__construct($translation_manager, $title_resolver);
     $this->negotiation = $negotiation;
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * Sets the Container associated with this Controller.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+   *   A ContainerInterface instance.
+   *
+   * @api
+   */
+  public function setContainer(ContainerInterface $container = NULL) {
+    $this->container = $container;
   }
 
   /**
@@ -90,14 +127,15 @@ public function on403Html(FlattenException $exception, Request $request) {
     $system_path = $request->attributes->get('_system_path');
     watchdog('access denied', $system_path, NULL, WATCHDOG_WARNING);
 
-    $path = $this->container->get('path.alias_manager')->getSystemPath(\Drupal::config('system.site')->get('page.403'));
+    $system_config = $this->container->get('config.factory')->get('system.site');
+    $path = $this->container->get('path.alias_manager')->getSystemPath($system_config->get('page.403'));
     if ($path && $path != $system_path) {
       // Keep old path for reference, and to allow forms to redirect to it.
       if (!$request->query->has('destination')) {
         $request->query->set('destination', $system_path);
       }
 
-      $subrequest = Request::create($request->getBaseUrl() . '/' . $path, 'get', array('destination' => $system_path), $request->cookies->all(), array(), $request->server->all());
+      $subrequest = Request::create($request->getBaseUrl() . '/' . $path, 'get', array('destination' => $system_path, '_exception_statuscode' => 403), $request->cookies->all(), array(), $request->server->all());
 
       // The active trail is being statically cached from the parent request to
       // the subrequest, like any other static.  Unfortunately that means the
@@ -119,15 +157,15 @@ public function on403Html(FlattenException $exception, Request $request) {
       $response->setStatusCode(403, 'Access denied');
     }
     else {
+      $page_content = array(
+        '#markup' => t('You are not authorized to access this page.'),
+        '#title' => t('Access denied'),
+      );
 
-      // @todo Replace this block with something cleaner.
-      $return = t('You are not authorized to access this page.');
-      drupal_set_title(t('Access denied'));
-      drupal_set_page_content($return);
-      $page = element_info('page');
-      $content = drupal_render_page($page);
-
-      $response = new Response($content, 403);
+      $fragment = $this->createHtmlFragment($page_content, $request);
+      $page = $this->renderer->render($fragment, 403);
+      $response = new Response($this->renderer->renderPage($page), $page->getStatusCode());
+      return $response;
     }
 
     return $response;
@@ -173,7 +211,8 @@ public function on404Html(FlattenException $exception, Request $request) {
       //   that and sub-call the kernel rather than using meah().
       // @todo The create() method expects a slash-prefixed path, but we store a
       //   normal system path in the site_404 variable.
-      $subrequest = Request::create($request->getBaseUrl() . '/' . $path, 'get', array('destination' => $system_path), $request->cookies->all(), array(), $request->server->all());
+
+      $subrequest = Request::create($request->getBaseUrl() . '/' . $path, 'get', array('destination' => $system_path, '_exception_statuscode' => 403), $request->cookies->all(), array(), $request->server->all());
 
       // The active trail is being statically cached from the parent request to
       // the subrequest, like any other static.  Unfortunately that means the
@@ -195,14 +234,15 @@ public function on404Html(FlattenException $exception, Request $request) {
       $response->setStatusCode(404, 'Not Found');
     }
     else {
-      // @todo Replace this block with something cleaner.
-      $return = t('The requested page "@path" could not be found.', array('@path' => $request->getPathInfo()));
-      drupal_set_title(t('Page not found'));
-      drupal_set_page_content($return);
-      $page = element_info('page');
-      $content = drupal_render_page($page);
-
-      $response = new Response($content, 404);
+      $page_content = array(
+        '#markup' => t('The requested page "@path" could not be found.', array('@path' => $request->getPathInfo())),
+        '#title' => t('Page not found'),
+      );
+
+      $fragment = $this->createHtmlFragment($page_content, $request);
+      $page = $this->renderer->render($fragment, 404);
+      $response = new Response($this->renderer->renderPage($page), $page->getStatusCode());
+      return $response;
     }
 
     return $response;
@@ -264,16 +304,16 @@ public function on500Html(FlattenException $exception, Request $request) {
       drupal_set_message(t('%type: !message in %function (line %line of %file).', $error), $class);
     }
 
-    drupal_set_title(t('Error'));
-    // We fallback to a maintenance page at this point, because the page
-    // generation itself can generate errors.
-    $maintenance_page = array(
+    $page_content = array(
       '#theme' => 'maintenance_page',
       '#content' => t('The website has encountered an error. Please try again later.'),
+      '#page' => array(
+        '#title' => t('Error'),
+      ),
     );
-    $output = drupal_render($maintenance_page);
 
-    $response = new Response($output, 500);
+    $output = drupal_render($page_content);
+    $response = new Response($output);
     $response->setStatusCode(500, '500 Service unavailable (with message)');
 
     return $response;
diff --git a/core/lib/Drupal/Core/Controller/FormController.php b/core/lib/Drupal/Core/Controller/FormController.php
new file mode 100644
index 0000000..271de99
--- /dev/null
+++ b/core/lib/Drupal/Core/Controller/FormController.php
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Controller\FormController.
+ */
+
+namespace Drupal\Core\Controller;
+
+use Drupal\Core\Form\FormBuilderInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Common base class for form interstitial controllers.
+ *
+ * @todo Make this a trait in PHP 5.4.
+ */
+abstract class FormController {
+
+  /**
+   * The form definition. The format may vary depending on the child class.
+   *
+   * @var string
+   */
+  protected $formDefinition;
+
+  /**
+   * The controller resolver.
+   *
+   * @var \Drupal\Core\Controller\ControllerResolverInterface
+   */
+  protected $resolver;
+
+  /**
+   * The form builder.
+   *
+   * @var \Drupal\Core\Form\FormBuilderInterface
+   */
+  protected $formBuilder;
+
+  /**
+   * Constructs a new \Drupal\Core\Controller\FormController object.
+   *
+   * @param \Drupal\Core\Controller\ControllerResolverInterface $resolver
+   *   The controller resolver.
+   * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
+   *   The form builder.
+   */
+  public function __construct(ControllerResolverInterface $resolver, FormBuilderInterface $form_builder) {
+    $this->resolver = $resolver;
+    $this->formBuilder = $form_builder;
+  }
+
+  /**
+   * Invokes the form and returns the result.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   *
+   * @return array
+   *   The render array that results from invoking the controller.
+   */
+  public function getContentResult(Request $request) {
+    $form_object = $this->getFormObject($request, $this->formDefinition);
+
+    // Add the form and form_state to trick the getArguments method of the
+    // controller resolver.
+    $form_state = array();
+    $request->attributes->set('form', array());
+    $request->attributes->set('form_state', $form_state);
+    $args = $this->resolver->getArguments($request, array($form_object, 'buildForm'));
+    $request->attributes->remove('form');
+    $request->attributes->remove('form_state');
+
+    // Remove $form and $form_state from the arguments, and re-index them.
+    unset($args[0], $args[1]);
+    $form_state['build_info']['args'] = array_values($args);
+
+    $form_id = $this->formBuilder->getFormId($form_object, $form_state);
+    return $this->formBuilder->buildForm($form_id, $form_state);
+  }
+
+  /**
+   * Returns the object used to build the form.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request using this form.
+   * @param string $form_arg
+   *   Either a class name or a service ID.
+   *
+   * @return \Drupal\Core\Form\FormInterface
+   *   The form object to use.
+   */
+  abstract protected function getFormObject(Request $request, $form_arg);
+
+}
diff --git a/core/lib/Drupal/Core/Controller/HtmlControllerBase.php b/core/lib/Drupal/Core/Controller/HtmlControllerBase.php
new file mode 100644
index 0000000..bf7be8d
--- /dev/null
+++ b/core/lib/Drupal/Core/Controller/HtmlControllerBase.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Controller\HtmlControllerBase.
+ */
+
+namespace Drupal\Core\Controller;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\Core\Page\HtmlFragment;
+use Drupal\Core\Utility\Title;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Base class for HTML page-generating controllers.
+ */
+class HtmlControllerBase {
+
+  /**
+   * The translation manager service.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface
+   */
+  protected $translationManager;
+
+  /**
+   * The title resolver.
+   *
+   * @var \Drupal\Core\Controller\TitleResolver
+   */
+  protected $titleResolver;
+
+  /**
+   * Constructs a new HtmlControllerBase object.
+   *
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
+   *   The translation manager.
+   * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
+   *   The title resolver.
+   */
+  public function __construct(TranslationInterface $translation_manager, TitleResolverInterface $title_resolver) {
+    $this->translationManager = $translation_manager;
+    $this->titleResolver = $title_resolver;
+  }
+
+  /**
+   * Converts a render array into an HtmlFragment object.
+   *
+   * @param array|string $page_content
+   *   The page content area to display.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   *
+   * @return \Drupal\Core\Page\HtmlPage
+   *   A page object.
+   */
+  protected function createHtmlFragment($page_content, Request $request) {
+    // Allow controllers to return a HtmlFragment or a Response object directly.
+    if ($page_content instanceof HtmlFragment || $page_content instanceof Response) {
+      return $page_content;
+    }
+
+    if (!is_array($page_content)) {
+      $page_content = array(
+        'main' => array(
+          '#markup' => $page_content,
+        ),
+      );
+    }
+
+    $fragment = new HtmlFragment(drupal_render($page_content));
+
+    // A title defined in the return always wins.
+    if (isset($page_content['#title'])) {
+      $fragment->setTitle($page_content['#title'], Title::FILTER_XSS_ADMIN);
+    }
+    else if ($route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)) {
+      $fragment->setTitle($this->titleResolver->getTitle($request, $route), PASS_THROUGH);
+    }
+
+    return $fragment;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Controller/HtmlFormController.php b/core/lib/Drupal/Core/Controller/HtmlFormController.php
index db78383..2e92c80 100644
--- a/core/lib/Drupal/Core/Controller/HtmlFormController.php
+++ b/core/lib/Drupal/Core/Controller/HtmlFormController.php
@@ -2,20 +2,19 @@
 
 /**
  * @file
- * Contains \Drupal\Core\Controller\HtmlFormController.
+ * Contains \Drupal\Core\Controler\HtmlFormController.
  */
 
 namespace Drupal\Core\Controller;
 
+use Drupal\Core\Form\FormBuilderInterface;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\DependencyInjection\ContainerAwareInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Wrapping controller for forms that serve as the main page body.
  */
-class HtmlFormController implements ContainerAwareInterface {
+class HtmlFormController extends FormController {
 
   /**
    * The injection container for this object.
@@ -25,48 +24,19 @@ class HtmlFormController implements ContainerAwareInterface {
   protected $container;
 
   /**
-   * Injects the service container used by this object.
+   * The name of a class implementing FormInterface that defines a form.
    *
-   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
-   *   The service container this object should use.
+   * @var string
    */
-  public function setContainer(ContainerInterface $container = NULL) {
-    $this->container = $container;
-  }
+  protected $formClass;
 
   /**
-   * Controller method for generic HTML form pages.
-   *
-   * @param \Symfony\Component\HttpFoundation\Request $request
-   *   The request object.
-   * @param callable $_form
-   *   The name of the form class for this request.
-   *
-   * @return \Symfony\Component\HttpFoundation\Response
-   *   A response object.
+   * Constructs a new \Drupal\Core\Routing\Enhancer\FormEnhancer object.
    */
-  public function content(Request $request, $_form) {
-    $form_object = $this->getFormObject($request, $_form);
-
-    // Using reflection, find all of the parameters needed by the form in the
-    // request attributes, skipping $form and $form_state.
-
-    // At the form and form_state to trick the getArguments method of the
-    // controller resolver.
-    $form_state = array();
-    $request->attributes->set('form', array());
-    $request->attributes->set('form_state', $form_state);
-    $args = $this->container->get('controller_resolver')->getArguments($request, array($form_object, 'buildForm'));
-    $request->attributes->remove('form');
-    $request->attributes->remove('form_state');
-
-    // Remove $form and $form_state from the arguments, and re-index them.
-    unset($args[0], $args[1]);
-    $form_state['build_info']['args'] = array_values($args);
-
-    $form_builder = $this->container->get('form_builder');
-    $form_id = $form_builder->getFormId($form_object, $form_state);
-    return $form_builder->buildForm($form_id, $form_state);
+  public function __construct(ControllerResolverInterface $resolver, ContainerInterface $container, $class, FormBuilderInterface $form_builder) {
+    parent::__construct($resolver, $form_builder);
+    $this->container = $container;
+    $this->formDefinition = $class;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Controller/HtmlPageController.php b/core/lib/Drupal/Core/Controller/HtmlPageController.php
index f3358ad..1672939 100644
--- a/core/lib/Drupal/Core/Controller/HtmlPageController.php
+++ b/core/lib/Drupal/Core/Controller/HtmlPageController.php
@@ -8,22 +8,12 @@
 namespace Drupal\Core\Controller;
 
 use Drupal\Core\StringTranslation\TranslationInterface;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\HttpKernel\HttpKernelInterface;
 
 /**
  * Default controller for most HTML pages.
  */
-class HtmlPageController {
-
-  /**
-   * The HttpKernel object to use for subrequests.
-   *
-   * @var \Symfony\Component\HttpKernel\HttpKernelInterface
-   */
-  protected $httpKernel;
+class HtmlPageController extends HtmlControllerBase {
 
   /**
    * The controller resolver.
@@ -33,35 +23,19 @@ class HtmlPageController {
   protected $controllerResolver;
 
   /**
-   * The translation manager service.
-   *
-   * @var \Drupal\Core\StringTranslation\TranslationInterface
-   */
-  protected $translationManager;
-
-  /**
-   * The title resolver.
-   *
-   * @var \Drupal\Core\Controller\TitleResolver
-   */
-  protected $titleResolver;
-
-  /**
    * Constructs a new HtmlPageController.
    *
-   * @param \Symfony\Component\HttpKernel\HttpKernelInterface $kernel
    * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
    *   The controller resolver.
    * @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
    *   The translation manager.
-   * @param \Drupal\Core\Controller\TitleResolver $title_resolver
+   * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
    *   The title resolver.
    */
-  public function __construct(HttpKernelInterface $kernel, ControllerResolverInterface $controller_resolver, TranslationInterface $translation_manager, TitleResolver $title_resolver) {
-    $this->httpKernel = $kernel;
+  public function __construct(ControllerResolverInterface $controller_resolver, TranslationInterface $translation_manager, TitleResolverInterface $title_resolver) {
+    parent::__construct($translation_manager, $title_resolver);
+
     $this->controllerResolver = $controller_resolver;
-    $this->translationManager = $translation_manager;
-    $this->titleResolver = $title_resolver;
   }
 
   /**
@@ -76,38 +50,32 @@ public function __construct(HttpKernelInterface $kernel, ControllerResolverInter
    *   A response object.
    */
   public function content(Request $request, $_content) {
-    $callable = $this->controllerResolver->getControllerFromDefinition($_content);
-    $arguments = $this->controllerResolver->getArguments($request, $callable);
-    $page_content = call_user_func_array($callable, $arguments);
-    if ($page_content instanceof Response) {
-      return $page_content;
-    }
-    if (!is_array($page_content)) {
-      $page_content = array(
-        'main' => array(
-          '#markup' => $page_content,
-        ),
-      );
-    }
-    if (!isset($page_content['#title'])) {
-      $title = $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT));
-      // Ensure that #title will not be set if no title was returned.
-      if (isset($title)) {
-        $page_content['#title'] = $title;
-      }
-    }
-
-    $response = new Response(drupal_render_page($page_content));
-    return $response;
+    $page_content = $this->getContentResult($request, $_content);
+    return $this->createHtmlFragment($page_content, $request);
   }
 
   /**
-   * Translates a string to the current language or to a given language.
+   * Returns the result of invoking the sub-controller.
    *
-   * See the t() documentation for details.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   * @param mixed $controller_definition
+   *   A controller definition string, or a callable object/closure.
+   *
+   * @return array
+   *   The render array that results from invoking the controller.
    */
-  protected function t($string, array $args = array(), array $options = array()) {
-    return $this->translationManager->translate($string, $args, $options);
+  public function getContentResult(Request $request, $controller_definition) {
+    if ($controller_definition instanceof \Closure) {
+      $callable = $controller_definition;
+    }
+    else {
+      $callable = $this->controllerResolver->getControllerFromDefinition($controller_definition);
+    }
+    $arguments = $this->controllerResolver->getArguments($request, $callable);
+    $page_content = call_user_func_array($callable, $arguments);
+
+    return $page_content;
   }
 
 }
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php
index 9e6506d..ae66851 100644
--- a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php
@@ -26,7 +26,13 @@ public function process(ContainerBuilder $container) {
     }
     $access_manager = $container->getDefinition('access_manager');
     foreach ($container->findTaggedServiceIds('access_check') as $id => $attributes) {
-      $access_manager->addMethodCall('addCheckService', array($id));
+      $applies = array();
+      foreach ($attributes as $attribute) {
+        if (isset($attribute['applies_to'])) {
+          $applies[] = $attribute['applies_to'];
+        }
+      }
+      $access_manager->addMethodCall('addCheckService', array($id, $applies));
     }
   }
 }
diff --git a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
index 3dbbb39..4e875df 100644
--- a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
@@ -17,261 +17,33 @@
 class EntityType extends Plugin {
 
   /**
-   * The name of the entity type class.
+   * The class used to represent the entity type.
    *
-   * This is not provided manually, it will be added by the discovery mechanism.
+   * It must implement \Drupal\Core\Entity\EntityTypeInterface.
    *
    * @var string
    */
-  public $class;
+  public $entity_type_class = 'Drupal\Core\Entity\EntityType';
 
   /**
-   * The name of the entity type's base table.
-   *
-   * @todo This is only used by \Drupal\Core\Entity\DatabaseStorageController.
-   *
-   * @var string
-   */
-  public $base_table;
-
-  /**
-   * An associative array where the keys are the names of different controller
-   * types (listed below) and the values are the names of the classes that
-   * implement that controller:
-   * - storage: The name of the class that is used to load the objects. The
-   *   class must implement \Drupal\Core\Entity\EntityStorageControllerInterface.
-   * - form: An associative array where the keys are the names of the different
-   *   form operations (such as 'create', 'edit', or 'delete') and the values
-   *   are the names of the controller classes for those operations. The name of
-   *   the operation is passed also to the form controller's constructor, so
-   *   that one class can be used for multiple entity forms when the forms are
-   *   similar. The classes must implement
-   *   \Drupal\Core\Entity\EntityFormControllerInterface
-   * - list: The name of the class that provides listings of the entities. The
-   *   class must implement \Drupal\Core\Entity\EntityListControllerInterface.
-   * - render: The name of the class that is used to render the entities. The
-   *   class must implement \Drupal\Core\Entity\EntityViewBuilderInterface.
-   * - access: The name of the class that is used for access checks. The class
-   *   must implement \Drupal\Core\Entity\EntityAccessControllerInterface.
-   *   Defaults to \Drupal\Core\Entity\EntityAccessController.
-   * - translation: The name of the controller class that should be used to
-   *   handle the translation process. The class must implement
-   *   \Drupal\content_translation\ContentTranslationControllerInterface.
-   *
-   * @todo Interfaces from outside \Drupal\Core or \Drupal\Component should not
-   *   be used here.
-   *
-   * @var array
-   */
-  public $controllers = array(
-    'access' => 'Drupal\Core\Entity\EntityAccessController',
-  );
-
-  /**
-   * The name of the default administrative permission.
-   *
-   * The default \Drupal\Core\Entity\EntityAccessController class checks this
-   * permission for all operations in its checkAccess() method. Entities with
-   * more complex permissions can extend this class to do their own access
-   * checks.
-   *
-   * @var string (optional)
-   */
-  public $admin_permission;
-
-  /**
-   * Boolean indicating whether fields can be attached to entities of this type.
-   *
-   * @var bool (optional)
-   */
-  public $fieldable = FALSE;
-
-  /**
-   * Boolean indicating if the persistent cache of field data should be used.
-   *
-   * The persistent cache should usually only be disabled if a higher level
-   * persistent cache is available for the entity type. Defaults to TRUE.
-   *
-   * @var bool (optional)
-   */
-  public $field_cache = TRUE;
-
-  /**
-   * The human-readable name of the type.
-   *
-   * @ingroup plugin_translatable
-   *
-   * @var \Drupal\Core\Annotation\Translation
-   */
-  public $label;
-
-  /**
-   * The human-readable name of the entity bundles, e.g. Vocabulary.
-   *
-   * @ingroup plugin_translatable
-   *
-   * @var \Drupal\Core\Annotation\Translation
-   */
-  public $bundle_label;
-
-  /**
-   * The name of a function that returns the label of the entity.
-   *
-   * The function takes an entity and optional langcode argument, and returns
-   * the label of the entity. If langcode is omitted, the entity's default
-   * language is used. The entity label is the main string associated with an
-   * entity; for example, the title of a node or the subject of a comment. If
-   * there is an entity object property that defines the label, use the 'label'
-   * element of the 'entity_keys' return value component to provide this
-   * information (see below). If more complex logic is needed to determine the
-   * label of an entity, you can instead specify a callback function here, which
-   * will be called to determine the entity label. See also the
-   * \Drupal\Core\Entity\EntityInterface::label() method, which implements this
-   * logic.
-   *
-   * @var string (optional)
-   */
-  public $label_callback;
-
-  /**
-   * Boolean indicating whether entities should be statically cached during a page request.
-   *
-   * @todo This is only used by \Drupal\Core\Entity\DatabaseStorageController.
-   *
-   * @var bool (optional)
-   */
-  public $static_cache = TRUE;
-
-  /**
-   * Boolean indicating whether the rendered output of entities should be
-   * cached.
-   *
-   * @var bool (optional)
-   */
-  public $render_cache = TRUE;
-
-  /**
-   * Boolean indicating whether entities of this type have multilingual support.
-   *
-   * At an entity level, this indicates language support and at a bundle level
-   * this indicates translation support.
-   *
-   * @var bool (optional)
-   */
-  public $translatable = FALSE;
-
-  /**
-   * @todo content_translation_entity_info_alter() uses this but it is undocumented.
+   * @todo content_translation_entity_info_alter() uses this but it is
+   *   undocumented. Fix in https://drupal.org/node/1968970.
    *
    * @var array
    */
   public $translation = array();
 
   /**
-   * The name of the entity type for which bundles are provided.
-   *
-   * It can be used by other modules to act accordingly; for example,
-   * the Field UI module uses it to add operation links to manage fields and
-   * displays.
-   *
-   * @var string
-   */
-  public $bundle_of;
-
-  /**
-   * An array describing how the Field API can extract certain information from
-   * objects of this entity type:
-   * - id: The name of the property that contains the primary ID of the entity.
-   *   Every entity object passed to the Field API must have this property and
-   *   its value must be numeric.
-   * - revision: (optional) The name of the property that contains the revision
-   *   ID of the entity. The Field API assumes that all revision IDs are unique
-   *   across all entities of a type. This entry can be omitted if the entities
-   *   of this type are not versionable.
-   * - bundle: (optional) The name of the property that contains the bundle name
-   *   for the entity. The bundle name defines which set of fields are attached
-   *   to the entity (e.g. what nodes call "content type"). This entry can be
-   *   omitted if this entity type exposes a single bundle (such that all
-   *   entities have the same collection of fields). The name of this single
-   *   bundle will be the same as the entity type.
-   * - label: The name of the property that contains the entity label. For
-   *   example, if the entity's label is located in $entity->subject, then
-   *   'subject' should be specified here. If complex logic is required to build
-   *   the label, a 'label_callback' should be defined instead (see the
-   *   $label_callback block above for details).
-   * - uuid (optional): The name of the property that contains the universally
-   *   unique identifier of the entity, which is used to distinctly identify an
-   *   entity across different systems.
-   *
-   * @var array
-   */
-  public $entity_keys = array(
-    'revision' => '',
-    'bundle' => '',
-  );
-
-  /**
-   * An array describing how the Field API can extract the information it needs
-   * from the bundle objects for this type (e.g Vocabulary objects for terms;
-   * not applicable for nodes):
-   * - bundle: The name of the property that contains the name of the bundle
-   *   object.
-   *
-   * This entry can be omitted if this type's bundles do not exist as standalone
-   * objects.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public $bundle_keys;
+  public function get() {
+    $values = $this->definition;
 
-  /**
-   * The name of the entity type which provides bundles.
-   *
-   * @var string (optional)
-   */
-  public $bundle_entity_type = 'bundle';
+    // Use the specified entity type class, and remove it before instantiating.
+    $class = $values['entity_type_class'];
+    unset($values['entity_type_class']);
 
-  /**
-   * Link templates using the URI template syntax.
-   *
-   * Links are an array of standard link relations to the URI template that
-   * should be used for them. Where possible, link relationships should use
-   * established IANA relationships rather than custom relationships.
-   *
-   * Every entity type should, at minimum, define "canonical", which is the
-   * pattern for URIs to that entity. Even if the entity will have no HTML page
-   * exposed to users it should still have a canonical URI in order to be
-   * compatible with web services. Entities that will be user-editable via an
-   * HTML page must also define an "edit-form" relationship.
-   *
-   * By default, the following placeholders are supported:
-   * - entityType: The machine name of the entity type.
-   * - bundle: The bundle machine name of the entity.
-   * - id: The unique ID of the entity.
-   * - uuid: The UUID of the entity.
-   * - [entityType]: The entity type itself will also be a valid token for the
-   *   ID of the entity. For instance, a placeholder of {node} used on the Node
-   *   class would have the same value as {id}. This is generally preferred
-   *   over "id" for better self-documentation.
-   *
-   * Specific entity types may also expand upon this list by overriding the
-   * uriPlaceholderReplacements() method.
-   *
-   * @link http://www.iana.org/assignments/link-relations/link-relations.xml @endlink
-   * @link http://tools.ietf.org/html/rfc6570 @endlink
-   *
-   * @var array
-   */
-  public $links = array();
-
-  /**
-   * Specifies whether a module exposing permissions for the current entity type
-   * should use entity-type level granularity, bundle level granularity or just
-   * skip this entity. The allowed values are respectively "entity_type",
-   * "bundle" or FALSE.
-   *
-   * @var string|bool (optional)
-   */
-  public $permission_granularity = 'entity_type';
+    return new $class($values);
+  }
 
 }
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index 8bdaed8..7c68f0a 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -179,8 +179,7 @@ public function setNewRevision($value = TRUE) {
    * {@inheritdoc}
    */
   public function isNewRevision() {
-    $info = $this->entityInfo();
-    return $this->newRevision || (!empty($info['entity_keys']['revision']) && !$this->getRevisionId());
+    return $this->newRevision || ($this->entityInfo()->hasKey('revision') && !$this->getRevisionId());
   }
 
   /**
@@ -712,7 +711,7 @@ public function addTranslation($langcode, array $values = array()) {
     // Instantiate a new empty entity so default values will be populated in the
     // specified language.
     $info = $this->entityInfo();
-    $default_values = array($info['entity_keys']['bundle'] => $this->bundle, 'langcode' => $langcode);
+    $default_values = array($info->getKey('bundle') => $this->bundle, 'langcode' => $langcode);
     $entity = \Drupal::entityManager()
       ->getStorageController($this->entityType())
       ->create($default_values);
@@ -898,17 +897,17 @@ public function createDuplicate() {
 
     $duplicate = clone $this;
     $entity_info = $this->entityInfo();
-    $duplicate->{$entity_info['entity_keys']['id']}->value = NULL;
+    $duplicate->{$entity_info->getKey('id')}->value = NULL;
 
     // Check if the entity type supports UUIDs and generate a new one if so.
-    if (!empty($entity_info['entity_keys']['uuid'])) {
+    if ($entity_info->hasKey('uuid')) {
       // @todo Inject the UUID service into the Entity class once possible.
-      $duplicate->{$entity_info['entity_keys']['uuid']}->value = \Drupal::service('uuid')->generate();
+      $duplicate->{$entity_info->getKey('uuid')}->value = \Drupal::service('uuid')->generate();
     }
 
     // Check whether the entity type supports revisions and initialize it if so.
-    if (!empty($entity_info['entity_keys']['revision'])) {
-      $duplicate->{$entity_info['entity_keys']['revision']}->value = NULL;
+    if ($entity_info->hasKey('revision')) {
+      $duplicate->{$entity_info->getKey('revision')}->value = NULL;
     }
 
     return $duplicate;
@@ -954,11 +953,12 @@ public function label($langcode = NULL) {
     if (!isset($langcode)) {
       $langcode = $this->activeLangcode;
     }
-    if (isset($entity_info['label_callback']) && function_exists($entity_info['label_callback'])) {
-      $label = $entity_info['label_callback']($this, $langcode);
+    // @todo Convert to is_callable() and call_user_func().
+    if (($label_callback = $entity_info->getLabelCallback()) && function_exists($label_callback)) {
+      $label = $label_callback($this, $langcode);
     }
-    elseif (!empty($entity_info['entity_keys']['label']) && isset($this->{$entity_info['entity_keys']['label']})) {
-      $label = $this->{$entity_info['entity_keys']['label']}->value;
+    elseif (($label_key = $entity_info->getKey('label')) && isset($this->{$label_key})) {
+      $label = $this->{$label_key}->value;
     }
     return $label;
   }
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityFormController.php b/core/lib/Drupal/Core/Entity/ContentEntityFormController.php
index 7f613bc..de83f56 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityFormController.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityFormController.php
@@ -50,8 +50,7 @@ public function form(array $form, array &$form_state) {
     $entity = $this->entity;
     // @todo Exploit the Field API to generate the default widgets for the
     // entity fields.
-    $info = $entity->entityInfo();
-    if (!empty($info['fieldable'])) {
+    if ($entity->entityInfo()->isFieldable()) {
       field_attach_form($entity, $form, $form_state, $this->getFormLangcode($form_state));
     }
 
@@ -132,14 +131,14 @@ public function isDefaultFormLangcode(array $form_state) {
   public function buildEntity(array $form, array &$form_state) {
     $entity = clone $this->entity;
     $entity_type = $entity->entityType();
-    $info = entity_get_info($entity_type);
+    $info = \Drupal::entityManager()->getDefinition($entity_type);
 
     // @todo Exploit the Entity Field API to process the submitted field values.
     // Copy top-level form values that are entity fields but not handled by
     // field API without changing existing entity fields that are not being
     // edited by this form. Values of fields handled by field API are copied
     // by field_attach_extract_form_values() below.
-    $values_excluding_fields = $info['fieldable'] ? array_diff_key($form_state['values'], field_info_instances($entity_type, $entity->bundle())) : $form_state['values'];
+    $values_excluding_fields = $info->isFieldable() ? array_diff_key($form_state['values'], field_info_instances($entity_type, $entity->bundle())) : $form_state['values'];
     $definitions = $entity->getPropertyDefinitions();
     foreach ($values_excluding_fields as $key => $value) {
       if (isset($definitions[$key])) {
@@ -155,7 +154,7 @@ public function buildEntity(array $form, array &$form_state) {
     }
 
     // Invoke field API for copying field values.
-    if ($info['fieldable']) {
+    if ($info->isFieldable()) {
       field_attach_extract_form_values($entity, $form, $form_state, array('langcode' => $this->getFormLangcode($form_state)));
     }
     return $entity;
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
index f258353..bfbdfb4 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Uuid\UuidInterface;
 use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Component\Utility\NestedArray;
@@ -60,9 +61,8 @@ class DatabaseStorageController extends EntityStorageControllerBase {
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
       $container->get('database'),
       $container->get('uuid')
@@ -72,36 +72,24 @@ public static function createInstance(ContainerInterface $container, $entity_typ
   /**
    * Constructs a DatabaseStorageController object.
    *
-   * @param string $entity_type
-   *   The entity type for which the instance is created.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Database\Connection $database
    *   The database connection to be used.
    * @param \Drupal\Component\Uuid\UuidInterface $uuid_service
    *   The UUID service.
    */
-  public function __construct($entity_type, array $entity_info, Connection $database, UuidInterface $uuid_service) {
-    parent::__construct($entity_type, $entity_info);
+  public function __construct(EntityTypeInterface $entity_info, Connection $database, UuidInterface $uuid_service) {
+    parent::__construct($entity_info);
 
     $this->database = $database;
     $this->uuidService = $uuid_service;
 
     // Check if the entity type supports IDs.
-    if (isset($this->entityInfo['entity_keys']['id'])) {
-      $this->idKey = $this->entityInfo['entity_keys']['id'];
-    }
-    else {
-      $this->idKey = FALSE;
-    }
+    $this->idKey = $this->entityInfo->getKey('id');
 
     // Check if the entity type supports UUIDs.
-    if (!empty($this->entityInfo['entity_keys']['uuid'])) {
-      $this->uuidKey = $this->entityInfo['entity_keys']['uuid'];
-    }
-    else {
-      $this->uuidKey = FALSE;
-    }
+    $this->uuidKey = $this->entityInfo->getKey('uuid');
   }
 
   /**
@@ -133,11 +121,11 @@ public function loadMultiple(array $ids = NULL) {
       // Build and execute the query.
       $query_result = $this->buildQuery($ids)->execute();
 
-      if (!empty($this->entityInfo['class'])) {
+      if ($class = $this->entityInfo->getClass()) {
         // We provide the necessary arguments for PDO to create objects of the
         // specified entity class.
         // @see \Drupal\Core\Entity\EntityInterface::__construct()
-        $query_result->setFetchMode(\PDO::FETCH_CLASS, $this->entityInfo['class'], array(array(), $this->entityType));
+        $query_result->setFetchMode(\PDO::FETCH_CLASS, $class, array(array(), $this->entityType));
       }
       $queried_entities = $query_result->fetchAllAssoc($this->idKey);
     }
@@ -229,12 +217,12 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value
    *   A SelectQuery object for loading the entity.
    */
   protected function buildQuery($ids, $revision_id = FALSE) {
-    $query = $this->database->select($this->entityInfo['base_table'], 'base');
+    $query = $this->database->select($this->entityInfo->getBaseTable(), 'base');
 
     $query->addTag($this->entityType . '_load_multiple');
 
     // Add fields from the {entity} table.
-    $entity_fields = drupal_schema_fields_sql($this->entityInfo['base_table']);
+    $entity_fields = drupal_schema_fields_sql($this->entityInfo->getBaseTable());
     $query->fields('base', $entity_fields);
 
     if ($ids) {
@@ -248,7 +236,7 @@ protected function buildQuery($ids, $revision_id = FALSE) {
    * {@inheritdoc}
    */
   public function create(array $values) {
-    $entity_class = $this->entityInfo['class'];
+    $entity_class = $this->entityInfo->getClass();
     $entity_class::preCreate($this, $values);
 
     $entity = new $entity_class($values, $this->entityType);
@@ -277,14 +265,14 @@ public function delete(array $entities) {
     $transaction = $this->database->startTransaction();
 
     try {
-      $entity_class = $this->entityInfo['class'];
+      $entity_class = $this->entityInfo->getClass();
       $entity_class::preDelete($this, $entities);
       foreach ($entities as $entity) {
         $this->invokeHook('predelete', $entity);
       }
       $ids = array_keys($entities);
 
-      $this->database->delete($this->entityInfo['base_table'])
+      $this->database->delete($this->entityInfo->getBaseTable())
         ->condition($this->idKey, $ids, 'IN')
         ->execute();
 
@@ -320,13 +308,13 @@ public function save(EntityInterface $entity) {
       $this->invokeHook('presave', $entity);
 
       if (!$entity->isNew()) {
-        $return = drupal_write_record($this->entityInfo['base_table'], $entity, $this->idKey);
+        $return = drupal_write_record($this->entityInfo->getBaseTable(), $entity, $this->idKey);
         $this->resetCache(array($entity->id()));
         $entity->postSave($this, TRUE);
         $this->invokeHook('update', $entity);
       }
       else {
-        $return = drupal_write_record($this->entityInfo['base_table'], $entity);
+        $return = drupal_write_record($this->entityInfo->getBaseTable(), $entity);
         // Reset general caches, but keep caches specific to certain entities.
         $this->resetCache(array());
 
diff --git a/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php b/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php
index 2cf8d47..0bbbef8 100644
--- a/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php
+++ b/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php
@@ -7,10 +7,13 @@
 
 namespace Drupal\Core\Entity\Enhancer;
 
+use Drupal\Core\Controller\ControllerResolverInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\HtmlEntityFormController;
+use Drupal\Core\Form\FormBuilderInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Drupal\Core\ContentNegotiation;
 
 /**
  * Enhances an entity form route with the appropriate controller.
@@ -18,38 +21,57 @@
 class EntityRouteEnhancer implements RouteEnhancerInterface {
 
   /**
-   * Content negotiation library.
+   * The controller resolver.
    *
-   * @var \Drupal\Core\ContentNegotiation
+   * @var \Drupal\Core\Controller\ControllerResolverInterface
    */
-  protected $negotiation;
+  protected $resolver;
 
   /**
-   * Constructs a new \Drupal\Core\Entity\Enhancer\EntityRouteEnhancer.
+   * The entity manager service.
    *
-   * @param \Drupal\Core\ContentNegotiation $negotiation
-   *   The content negotiation library.
+   * @var \Drupal\Core\Entity\EntityManagerInterface
    */
-  public function __construct(ContentNegotiation $negotiation) {
-    $this->negotiation = $negotiation;
+  protected $manager;
+
+  /**
+   * The form builder.
+   *
+   * @var \Drupal\Core\Form\FormBuilderInterface
+   */
+  protected $formBuilder;
+
+  /**
+   * Constructs a new EntityRouteEnhancer object.
+   *
+   * @param \Drupal\Core\Controller\ControllerResolverInterface $resolver
+   *   The controller resolver.
+   * @param \Drupal\Core\Entity\EntityManagerInterface $manager
+   *   The entity manager.
+   * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
+   *   The form builder.
+   */
+  public function __construct(ControllerResolverInterface $resolver, EntityManagerInterface $manager, FormBuilderInterface $form_builder) {
+    $this->resolver = $resolver;
+    $this->manager = $manager;
+    $this->formBuilder = $form_builder;
   }
 
   /**
    * {@inheritdoc}
    */
   public function enhance(array $defaults, Request $request) {
-    if (empty($defaults['_controller']) && $this->negotiation->getContentType($request) === 'html') {
+    if (empty($defaults['_content'])) {
       if (!empty($defaults['_entity_form'])) {
-        $defaults['_controller'] = '\Drupal\Core\Entity\HtmlEntityFormController::content';
+        $wrapper = new HtmlEntityFormController($this->resolver, $this->manager, $this->formBuilder, $defaults['_entity_form']);
+        $defaults['_content'] = array($wrapper, 'getContentResult');
       }
       elseif (!empty($defaults['_entity_list'])) {
-        $defaults['_controller'] = 'controller.page:content';
         $defaults['_content'] = '\Drupal\Core\Entity\Controller\EntityListController::listing';
         $defaults['entity_type'] = $defaults['_entity_list'];
         unset($defaults['_entity_list']);
       }
       elseif (!empty($defaults['_entity_view'])) {
-        $defaults['_controller'] = 'controller.page:content';
         $defaults['_content'] = '\Drupal\Core\Entity\Controller\EntityViewController::view';
         if (strpos($defaults['_entity_view'], '.') !== FALSE) {
           // The _entity_view entry is of the form entity_type.view_mode.
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index b0b905d..178fe51 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -109,11 +109,12 @@ public function bundle() {
   public function label($langcode = NULL) {
     $label = NULL;
     $entity_info = $this->entityInfo();
-    if (isset($entity_info['label_callback']) && function_exists($entity_info['label_callback'])) {
-      $label = $entity_info['label_callback']($this, $langcode);
+    // @todo Convert to is_callable() and call_user_func().
+    if (($label_callback = $entity_info->getLabelCallback()) && function_exists($label_callback)) {
+      $label = $label_callback($this, $langcode);
     }
-    elseif (!empty($entity_info['entity_keys']['label']) && isset($this->{$entity_info['entity_keys']['label']})) {
-      $label = $this->{$entity_info['entity_keys']['label']};
+    elseif (($label_key = $entity_info->getKey('label')) && isset($this->{$label_key})) {
+      $label = $this->{$label_key};
     }
     return $label;
   }
@@ -150,7 +151,7 @@ public function uri($rel = 'canonical') {
     $entity_info = $this->entityInfo();
 
     // The links array might contain URI templates set in annotations.
-    $link_templates = isset($entity_info['links']) ? $entity_info['links'] : array();
+    $link_templates = $entity_info->getLinkTemplates();
 
     $template = NULL;
     if (isset($link_templates[$rel])) {
@@ -185,12 +186,13 @@ public function uri($rel = 'canonical') {
     if (isset($bundles[$bundle]['uri_callback'])) {
       $uri_callback = $bundles[$bundle]['uri_callback'];
     }
-    elseif (isset($entity_info['uri_callback'])) {
-      $uri_callback = $entity_info['uri_callback'];
+    elseif ($entity_uri_callback = $entity_info->getUriCallback()) {
+      $uri_callback = $entity_uri_callback;
     }
 
     // Invoke the callback to get the URI. If there is no callback, use the
     // default URI format.
+    // @todo Convert to is_callable() and call_user_func().
     if (isset($uri_callback) && function_exists($uri_callback)) {
       $uri = $uri_callback($this);
     }
@@ -243,8 +245,7 @@ protected function uriPlaceholderReplacements() {
    *   An array of link relationships supported by this entity.
    */
   public function uriRelationships() {
-    $entity_info = $this->entityInfo();
-    return isset($entity_info['links']) ? array_keys($entity_info['links']) : array();
+    return array_keys($this->entityInfo()->getLinkTemplates());
   }
 
 
@@ -296,12 +297,12 @@ public function delete() {
   public function createDuplicate() {
     $duplicate = clone $this;
     $entity_info = $this->entityInfo();
-    $duplicate->{$entity_info['entity_keys']['id']} = NULL;
+    $duplicate->{$entity_info->getKey('id')} = NULL;
 
     // Check if the entity type supports UUIDs and generate a new one if so.
-    if (!empty($entity_info['entity_keys']['uuid'])) {
+    if ($entity_info->hasKey('uuid')) {
       // @todo Inject the UUID service into the Entity class once possible.
-      $duplicate->{$entity_info['entity_keys']['uuid']} = \Drupal::service('uuid')->generate();
+      $duplicate->{$entity_info->getKey('uuid')} = \Drupal::service('uuid')->generate();
     }
     return $duplicate;
   }
diff --git a/core/lib/Drupal/Core/Entity/EntityAccessCheck.php b/core/lib/Drupal/Core/Entity/EntityAccessCheck.php
index 06ad0b7..1e3b0ae 100644
--- a/core/lib/Drupal/Core/Entity/EntityAccessCheck.php
+++ b/core/lib/Drupal/Core/Entity/EntityAccessCheck.php
@@ -7,23 +7,15 @@
 
 namespace Drupal\Core\Entity;
 
-use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
-use Drupal\Core\Access\StaticAccessCheckInterface;
 
 /**
  * Provides a generic access checker for entities.
  */
-class EntityAccessCheck implements StaticAccessCheckInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function appliesTo() {
-    return array('_entity_access');
-  }
+class EntityAccessCheck implements AccessInterface {
 
   /**
    * Implements \Drupal\Core\Access\AccessCheckInterface::access().
diff --git a/core/lib/Drupal/Core/Entity/EntityAccessController.php b/core/lib/Drupal/Core/Entity/EntityAccessController.php
index c747cca..f18cd2c 100644
--- a/core/lib/Drupal/Core/Entity/EntityAccessController.php
+++ b/core/lib/Drupal/Core/Entity/EntityAccessController.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Entity;
 
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
@@ -35,7 +36,7 @@ class EntityAccessController implements EntityAccessControllerInterface {
   /**
    * The entity info array.
    *
-   * @var array
+   * @var \Drupal\Core\Entity\EntityTypeInterface
    */
   protected $entityInfo;
 
@@ -49,13 +50,11 @@ class EntityAccessController implements EntityAccessControllerInterface {
   /**
    * Constructs an access controller instance.
    *
-   * @param string $entity_type
-   *   The entity type of the access controller instance.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    */
-  public function __construct($entity_type, array $entity_info) {
-    $this->entityType = $entity_type;
+  public function __construct(EntityTypeInterface $entity_info) {
+    $this->entityType = $entity_info->id();
     $this->entityInfo = $entity_info;
   }
 
@@ -137,8 +136,8 @@ protected function processAccessHookResults(array $access) {
    *   could not be determined.
    */
   protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
-    if (!empty($this->entityInfo['admin_permission'])) {
-      return $account->hasPermission($this->entityInfo['admin_permission']);
+    if ($admin_permission = $this->entityInfo->getAdminPermission()) {
+      return $account->hasPermission($admin_permission);
     }
     else {
       return NULL;
@@ -258,8 +257,8 @@ public function createAccess($entity_bundle = NULL, AccountInterface $account =
    *   could not be determined.
    */
   protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
-    if (!empty($this->entityInfo['admin_permission'])) {
-      return $account->hasPermission($this->entityInfo['admin_permission']);
+    if ($admin_permission = $this->entityInfo->getAdminPermission()) {
+      return $account->hasPermission($admin_permission);
     }
     else {
       return NULL;
diff --git a/core/lib/Drupal/Core/Entity/EntityControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityControllerInterface.php
index 79232c1..cdcffd3 100644
--- a/core/lib/Drupal/Core/Entity/EntityControllerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityControllerInterface.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Entity;
 
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -30,14 +31,12 @@
    *
    * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
    *   The service container this object should use.
-   * @param string $entity_type
-   *   The entity type which the controller handles.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    *
    * @return static
    *   A new instance of the entity controller.
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info);
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info);
 
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php b/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php
index e3268bf..1cf7d87 100644
--- a/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php
+++ b/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Entity;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
@@ -15,7 +15,7 @@
 /**
  * Defines an access checker for entity creation.
  */
-class EntityCreateAccessCheck implements StaticAccessCheckInterface {
+class EntityCreateAccessCheck implements AccessInterface {
 
   /**
    * The entity manager.
@@ -44,13 +44,6 @@ public function __construct(EntityManagerInterface $entity_manager) {
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    return array($this->requirementsKey);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     list($entity_type, $bundle) = explode(':', $route->getRequirement($this->requirementsKey) . ':');
 
diff --git a/core/lib/Drupal/Core/Entity/EntityFormController.php b/core/lib/Drupal/Core/Entity/EntityFormController.php
index 8057ca7..2ea0fbe 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormController.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormController.php
@@ -138,8 +138,7 @@ public function form(array $form, array &$form_state) {
     $entity = $this->entity;
     // @todo Exploit the Field API to generate the default widgets for the
     // entity properties.
-    $info = $entity->entityInfo();
-    if (!empty($info['fieldable'])) {
+    if ($entity->entityInfo()->isFieldable()) {
       field_attach_form($entity, $form, $form_state, $this->getFormLangcode($form_state));
     }
 
diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php
index bf01326..66ed701 100644
--- a/core/lib/Drupal/Core/Entity/EntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityInterface.php
@@ -228,7 +228,7 @@ public function createDuplicate();
   /**
    * Returns the info of the type of the entity.
    *
-   * @see entity_get_info()
+   * @return \Drupal\Core\Entity\EntityTypeInterface
    */
   public function entityInfo();
 
diff --git a/core/lib/Drupal/Core/Entity/EntityListController.php b/core/lib/Drupal/Core/Entity/EntityListController.php
index 64c1c43..fdbc6a9 100644
--- a/core/lib/Drupal/Core/Entity/EntityListController.php
+++ b/core/lib/Drupal/Core/Entity/EntityListController.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Entity;
 
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -41,9 +42,7 @@ class EntityListController implements EntityListControllerInterface, EntityContr
   /**
    * The entity info array.
    *
-   * @var array
-   *
-   * @see entity_get_info()
+   * @var \Drupal\Core\Entity\EntityTypeInterface
    */
   protected $entityInfo;
 
@@ -57,11 +56,10 @@ class EntityListController implements EntityListControllerInterface, EntityContr
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
-      $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getStorageController($entity_info->id()),
       $container->get('module_handler')
     );
   }
@@ -69,17 +67,15 @@ public static function createInstance(ContainerInterface $container, $entity_typ
   /**
    * Constructs a new EntityListController object.
    *
-   * @param string $entity_type
-   *   The type of entity to be listed.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler) {
-    $this->entityType = $entity_type;
+  public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler) {
+    $this->entityType = $entity_info->id();
     $this->storage = $storage;
     $this->entityInfo = $entity_info;
     $this->moduleHandler = $module_handler;
@@ -204,7 +200,7 @@ public function render() {
       '#header' => $this->buildHeader(),
       '#title' => $this->getTitle(),
       '#rows' => array(),
-      '#empty' => $this->t('There is no @label yet.', array('@label' => $this->entityInfo['label'])),
+      '#empty' => $this->t('There is no @label yet.', array('@label' => $this->entityInfo->getLabel())),
     );
     foreach ($this->load() as $entity) {
       if ($row = $this->buildRow($entity)) {
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 83a671c..bffc91b 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -34,7 +34,7 @@
  *
  * @see \Drupal\Core\Entity\Annotation\EntityType
  * @see \Drupal\Core\Entity\EntityInterface
- * @see entity_get_info()
+ * @see \Drupal\Core\Entity\EntityTypeInterface
  * @see hook_entity_info_alter()
  */
 class EntityManager extends PluginManagerBase implements EntityManagerInterface {
@@ -160,28 +160,26 @@ public function clearCachedDefinitions() {
    * {@inheritdoc}
    */
   public function hasController($entity_type, $controller_type) {
-    $definition = $this->getDefinition($entity_type);
-    return !empty($definition['controllers'][$controller_type]);
+    if ($definition = $this->getDefinition($entity_type)) {
+      return $definition->hasController($controller_type);
+    }
+    return FALSE;
   }
 
   /**
    * {@inheritdoc}
    */
   public function getControllerClass($entity_type, $controller_type, $nested = NULL) {
-    $definition = $this->getDefinition($entity_type);
-    if (!$definition) {
+    $info = $this->getDefinition($entity_type);
+    if (!$info) {
       throw new \InvalidArgumentException(sprintf('The %s entity type does not exist.', $entity_type));
     }
-    $definition = $definition['controllers'];
-    if (!$definition) {
-      throw new \InvalidArgumentException(sprintf('The entity type (%s) does not exist.', $entity_type));
-    }
 
-    if (empty($definition[$controller_type])) {
+    if (!$info->hasController($controller_type)) {
       throw new \InvalidArgumentException(sprintf('The entity type (%s) did not specify a %s controller.', $entity_type, $controller_type));
     }
 
-    $class = $definition[$controller_type];
+    $class = $info->getController($controller_type);
 
     // Some class definitions can be nested.
     if (isset($nested)) {
@@ -213,7 +211,7 @@ public function getListController($entity_type) {
     if (!isset($this->controllers['listing'][$entity_type])) {
       $class = $this->getControllerClass($entity_type, 'list');
       if (in_array('Drupal\Core\Entity\EntityControllerInterface', class_implements($class))) {
-        $this->controllers['listing'][$entity_type] = $class::createInstance($this->container, $entity_type, $this->getDefinition($entity_type));
+        $this->controllers['listing'][$entity_type] = $class::createInstance($this->container, $this->getDefinition($entity_type));
       }
       else {
         $this->controllers['listing'][$entity_type] = new $class($entity_type, $this->getStorageController($entity_type));
@@ -277,10 +275,10 @@ protected function getController($entity_type, $controller_type) {
     if (!isset($this->controllers[$controller_type][$entity_type])) {
       $class = $this->getControllerClass($entity_type, $controller_type);
       if (in_array('Drupal\Core\Entity\EntityControllerInterface', class_implements($class))) {
-        $this->controllers[$controller_type][$entity_type] = $class::createInstance($this->container, $entity_type, $this->getDefinition($entity_type));
+        $this->controllers[$controller_type][$entity_type] = $class::createInstance($this->container, $this->getDefinition($entity_type));
       }
       else {
-        $this->controllers[$controller_type][$entity_type] = new $class($entity_type, $this->getDefinition($entity_type));
+        $this->controllers[$controller_type][$entity_type] = new $class($this->getDefinition($entity_type));
       }
     }
     return $this->controllers[$controller_type][$entity_type];
@@ -302,9 +300,9 @@ public function getAdminPath($entity_type, $bundle) {
     $admin_path = '';
     $entity_info = $this->getDefinition($entity_type);
     // Check for an entity type's admin base path.
-    if (isset($entity_info['links']['admin-form'])) {
-      $route_parameters[$entity_info['bundle_entity_type']] = $bundle;
-      $admin_path = \Drupal::urlGenerator()->getPathFromRoute($entity_info['links']['admin-form'], $route_parameters);
+    if ($admin_form = $entity_info->getLinkTemplate('admin-form')) {
+      $route_parameters[$entity_info->getBundleEntityType()] = $bundle;
+      $admin_path = \Drupal::urlGenerator()->getPathFromRoute($admin_form, $route_parameters);
     }
 
     return $admin_path;
@@ -318,7 +316,7 @@ public function getAdminRouteInfo($entity_type, $bundle) {
     return array(
       'route_name' => "field_ui.overview_$entity_type",
       'route_parameters' => array(
-        $entity_info['bundle_entity_type'] => $bundle,
+        $entity_info->getBundleEntityType() => $bundle,
       )
     );
   }
@@ -336,7 +334,9 @@ public function getFieldDefinitions($entity_type, $bundle = NULL) {
       else {
         // @todo: Refactor to allow for per-bundle overrides.
         // See https://drupal.org/node/2114707.
-        $class = $this->factory->getPluginClass($entity_type, $this->getDefinition($entity_type));
+        $entity_info = $this->getDefinition($entity_type);
+        $definition = array('class' => $entity_info->getClass());
+        $class = $this->factory->getPluginClass($entity_type, $definition);
 
         $this->entityFieldInfo[$entity_type] = array(
           'definitions' => $class::baseFieldDefinitions($entity_type),
@@ -367,8 +367,7 @@ public function getFieldDefinitions($entity_type, $bundle = NULL) {
         $this->moduleHandler->alter($hooks, $this->entityFieldInfo[$entity_type], $entity_type);
 
         // Ensure all basic fields are not defined as translatable.
-        $entity_info = $this->getDefinition($entity_type);
-        $keys = array_intersect_key(array_filter($entity_info['entity_keys']), array_flip(array('id', 'revision', 'uuid', 'bundle')));
+        $keys = array_intersect_key(array_filter($entity_info->getKeys()), array_flip(array('id', 'revision', 'uuid', 'bundle')));
         $untranslatable_fields = array_flip(array('langcode') + $keys);
         foreach (array('definitions', 'optional') as $key) {
           foreach ($this->entityFieldInfo[$entity_type][$key] as $field_name => &$definition) {
@@ -436,7 +435,7 @@ public function getAllBundleInfo() {
         // If no bundles are provided, use the entity type name and label.
         foreach ($this->getDefinitions() as $type => $entity_info) {
           if (!isset($this->bundleInfo[$type])) {
-            $this->bundleInfo[$type][$type]['label'] = $entity_info['label'];
+            $this->bundleInfo[$type][$type]['label'] = $entity_info->getLabel();
           }
         }
         $this->moduleHandler->alter('entity_bundle_info', $this->bundleInfo);
@@ -453,7 +452,7 @@ public function getAllBundleInfo() {
   public function getEntityTypeLabels() {
     $options = array();
     foreach ($this->getDefinitions() as $entity_type => $definition) {
-      $options[$entity_type] = $definition['label'];
+      $options[$entity_type] = $definition->getLabel();
     }
 
     return $options;
diff --git a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
index fdc9666..cb962e0 100644
--- a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
@@ -262,4 +262,23 @@ public function getBundleInfo($entity_type);
    */
   public function getTranslationFromContext(EntityInterface $entity, $langcode = NULL, $context = array());
 
+  /**
+   * Returns the entity type info for a specific entity type.
+   *
+   * @param string $entity_type_name
+   *   The name of the entity type.
+   *
+   * @return \Drupal\Core\Entity\EntityTypeInterface|null
+   *   Either the entity type object, or NULL if the name does not exist.
+   */
+  public function getDefinition($entity_type_name);
+
+  /**
+   * Returns an array of entity type info, keyed by entity type name.
+   *
+   * @return \Drupal\Core\Entity\EntityTypeInterface[]
+   *   An array of entity type objects.
+   */
+  public function getDefinitions();
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php
index e9d7fcb..8774ac2 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php
@@ -39,9 +39,7 @@
   /**
    * Array of information about the entity.
    *
-   * @var array
-   *
-   * @see entity_get_info()
+   * @var \Drupal\Core\Entity\EntityTypeInterface
    */
   protected $entityInfo;
 
@@ -64,16 +62,14 @@
   /**
    * Constructs an EntityStorageControllerBase instance.
    *
-   * @param string $entity_type
-   *   The entity type for which the instance is created.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    */
-  public function __construct($entity_type, $entity_info) {
-    $this->entityType = $entity_type;
+  public function __construct(EntityTypeInterface $entity_info) {
+    $this->entityType = $entity_info->id();
     $this->entityInfo = $entity_info;
     // Check if the entity type supports static caching of loaded entities.
-    $this->cache = !empty($this->entityInfo['static_cache']);
+    $this->cache = $this->entityInfo->isStaticallyCacheable();
   }
 
   /**
@@ -165,7 +161,7 @@ protected function invokeHook($hook, EntityInterface $entity) {
    *   Associative array of query results, keyed on the entity ID.
    */
   protected function postLoad(array &$queried_entities) {
-    $entity_class = $this->entityInfo['class'];
+    $entity_class = $this->entityInfo->getClass();
     $entity_class::postLoad($this, $queried_entities);
     // Call hook_entity_load().
     foreach (\Drupal::moduleHandler()->getImplementations('entity_load') as $module) {
diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php
new file mode 100644
index 0000000..1d86e02
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -0,0 +1,537 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityType.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Component\Utility\Unicode;
+
+/**
+ * Provides an implementation of an entity type and its metadata.
+ */
+class EntityType implements EntityTypeInterface {
+
+  /**
+   * Indicates whether entities should be statically cached.
+   *
+   * @var bool
+   */
+  protected $static_cache;
+
+  /**
+   * Indicates whether the rendered output of entities should be cached.
+   *
+   * @var bool
+   */
+  protected $render_cache;
+
+  /**
+   * Indicates if the persistent cache of field data should be used.
+   *
+   * @var bool
+   */
+  protected $field_cache;
+
+  /**
+   * An array of entity keys.
+   *
+   * @var array
+   */
+  protected $entity_keys = array();
+
+  /**
+   * The unique identifier of this entity type.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * The name of the provider of this entity type.
+   *
+   * @var string
+   */
+  protected $provider;
+
+  /**
+   * The name of the entity type class.
+   *
+   * @var string
+   */
+  protected $class;
+
+  /**
+   * An array of controllers.
+   *
+   * @var array
+   */
+  protected $controllers = array();
+
+  /**
+   * The name of the default administrative permission.
+   *
+   * @var string
+   */
+  protected $admin_permission;
+
+  /**
+   * The permission granularity level.
+   *
+   * The allowed values are respectively "entity_type", "bundle" or FALSE.
+   *
+   * @var string|bool
+   */
+  protected $permission_granularity;
+
+  /**
+   * An array describing how the Field API can extract the information it needs
+   * from the bundle objects for this type (e.g Vocabulary objects for terms;
+   * not applicable for nodes):
+   * - bundle: The name of the property that contains the name of the bundle
+   *   object.
+   *
+   * This entry can be omitted if this type's bundles do not exist as standalone
+   * objects.
+   *
+   * @var array
+   */
+  protected $bundle_keys = array();
+
+  /**
+   * Indicates whether fields can be attached to entities of this type.
+   *
+   * @var bool (optional)
+   */
+  protected $fieldable;
+
+  /**
+   * Link templates using the URI template syntax.
+   *
+   * @var array
+   */
+  protected $links = array();
+
+  /**
+   * The name of a callback that returns the label of the entity.
+   *
+   * @var string
+   */
+  protected $label_callback;
+
+  /**
+   * The name of the entity type which provides bundles.
+   *
+   * @var string
+   */
+  protected $bundle_entity_type;
+
+  /**
+   * The name of the entity type for which bundles are provided.
+   *
+   * @var string
+   */
+  protected $bundle_of;
+
+  /**
+   * The human-readable name of the entity bundles, e.g. Vocabulary.
+   *
+   * @var string
+   */
+  protected $bundle_label;
+
+  /**
+   * The name of the entity type's base table.
+   *
+   * @var string
+   */
+  protected $base_table;
+
+  /**
+   * The name of the entity type's revision data table.
+   *
+   * @var string
+   */
+  protected $revision_data_table;
+
+  /**
+   * The name of the entity type's revision table.
+   *
+   * @var string
+   */
+  protected $revision_table;
+
+  /**
+   * The name of the entity type's data table.
+   *
+   * @var string
+   */
+  protected $data_table;
+
+  /**
+   * Indicates whether entities of this type have multilingual support.
+   *
+   * @var bool
+   */
+  protected $translatable = FALSE;
+
+  /**
+   * Returns the config prefix used by the configuration entity type.
+   *
+   * @var string
+   */
+  protected $config_prefix;
+
+  /**
+   * The human-readable name of the type.
+   *
+   * @var string
+   */
+  protected $label;
+
+  /**
+   * A callable that can be used to provide the entity URI.
+   *
+   * @var callable
+   */
+  protected $uri_callback;
+
+  /**
+   * Constructs a new EntityType.
+   *
+   * @param array $definition
+   *   An array of values from the annotation.
+   */
+  public function __construct($definition) {
+    foreach ($definition as $property => $value) {
+      $this->{$property} = $value;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($property) {
+    return isset($this->{$property}) ? $this->{$property} : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function set($property, $value) {
+    $this->{$property} = $value;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isStaticallyCacheable() {
+    return isset($this->static_cache) ? $this->static_cache: TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isRenderCacheable() {
+    return isset($this->render_cache) ? $this->render_cache: TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isFieldDataCacheable() {
+    return isset($this->field_cache) ? $this->field_cache: TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getKeys() {
+    return $this->entity_keys + array('revision' => '', 'bundle' => '');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getKey($key) {
+    $keys = $this->getKeys();
+    return isset($keys[$key]) ? $keys[$key] : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasKey($key) {
+    $keys = $this->getKeys();
+    return !empty($keys[$key]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function id() {
+    return $this->id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getProvider() {
+    return $this->provider;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getClass() {
+    return $this->class;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setClass($class) {
+    $this->class = $class;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isSubclassOf($class) {
+    return is_subclass_of($this->getClass(), $class);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getControllers() {
+    return $this->controllers + array(
+      'access' => 'Drupal\Core\Entity\EntityAccessController',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getController($controller_type) {
+    $controllers = $this->getControllers();
+    return $controllers[$controller_type];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setController($controller_type, $value) {
+    $this->controllers[$controller_type] = $value;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasController($controller_type) {
+    $controllers = $this->getControllers();
+    return isset($controllers[$controller_type]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setForm($operation, $class) {
+    $this->controllers['form'][$operation] = $class;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setList($class) {
+    $this->controllers['list'] = $class;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAdminPermission() {
+    return $this->admin_permission ?: FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPermissionGranularity() {
+    return isset($this->permission_granularity) ? $this->permission_granularity : 'entity_type';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBundleKeys() {
+    return isset($this->bundle_keys) ? $this->bundle_keys : array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBundleKey($name) {
+    return isset($this->bundle_keys[$name]) ? $this->bundle_keys[$name] : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isFieldable() {
+    return isset($this->fieldable) ? $this->fieldable : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLinkTemplates() {
+    return isset($this->links) ? $this->links : array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLinkTemplate($key) {
+    $links = $this->getLinkTemplates();
+    return isset($links[$key]) ? $links[$key] : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasLinkTemplate($key) {
+    $links = $this->getLinkTemplates();
+    return isset($links[$key]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setLinkTemplate($key, $route_name) {
+    $this->links[$key] = $route_name;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLabelCallback() {
+    return isset($this->label_callback) ? $this->label_callback : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setLabelCallback($callback) {
+    $this->label_callback = $callback;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasLabelCallback() {
+    return isset($this->label_callback);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBundleEntityType() {
+    return isset($this->bundle_entity_type) ? $this->bundle_entity_type : 'bundle';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBundleOf() {
+    return isset($this->bundle_of) ? $this->bundle_of : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBundleLabel() {
+    return isset($this->bundle_label) ? $this->bundle_label : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBaseTable() {
+    return isset($this->base_table) ? $this->base_table : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isTranslatable() {
+    return !empty($this->translatable);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfigPrefix() {
+    return isset($this->config_prefix) ? $this->config_prefix : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRevisionDataTable() {
+    return isset($this->revision_data_table) ? $this->revision_data_table : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRevisionTable() {
+    return isset($this->revision_table) ? $this->revision_table : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDataTable() {
+    return isset($this->data_table) ? $this->data_table : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLabel() {
+    return isset($this->label) ? $this->label : '';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLowercaseLabel() {
+    return Unicode::strtolower($this->getLabel());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getUriCallback() {
+    return isset($this->uri_callback) ? $this->uri_callback : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUriCallback($callback) {
+    $this->uri_callback = $callback;
+    return $this;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
new file mode 100644
index 0000000..9a121d3
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
@@ -0,0 +1,531 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityTypeInterface.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Provides an interface for an entity type and its metadata.
+ */
+interface EntityTypeInterface {
+
+  /**
+   * Gets any arbitrary property.
+   *
+   * @param string $property
+   *   The property to retrieve.
+   *
+   * @return mixed
+   *   The value for that property, or NULL if the property does not exist.
+   */
+  public function get($property);
+
+  /**
+   * Sets a value to an arbitrary property.
+   *
+   * @param string $property
+   *   The property to use for the value.
+   * @param mixed $value
+   *   The value to set.
+   *
+   * @return static
+   */
+  public function set($property, $value);
+
+  /**
+   * Returns the unique identifier of the entity type.
+   *
+   * @return string
+   *   The unique identifier of the entity type.
+   */
+  public function id();
+
+  /**
+   * Returns the name of the provider of this entity type.
+   *
+   * @return string
+   *   The name of the provider of this entity type.
+   */
+  public function getProvider();
+
+  /**
+   * Returns the name of the entity type class.
+   *
+   * @return string
+   *   The name of the entity type class.
+   */
+  public function getClass();
+
+  /**
+   * Returns an array of entity keys.
+   *
+   * @return array
+   *   An array describing how the Field API can extract certain information
+   *   from objects of this entity type:
+   *   - id: The name of the property that contains the primary ID of the
+   *     entity. Every entity object passed to the Field API must have this
+   *     property and its value must be numeric.
+   *   - revision: (optional) The name of the property that contains the
+   *     revision ID of the entity. The Field API assumes that all revision IDs
+   *     are unique across all entities of a type. This entry can be omitted if
+   *     the entities of this type are not versionable.
+   *   - bundle: (optional) The name of the property that contains the bundle
+   *     name for the entity. The bundle name defines which set of fields are
+   *     attached to the entity (e.g. what nodes call "content type"). This
+   *     entry can be omitted if this entity type exposes a single bundle (such
+   *     that all entities have the same collection of fields). The name of this
+   *     single bundle will be the same as the entity type.
+   *   - label: The name of the property that contains the entity label. For
+   *     example, if the entity's label is located in $entity->subject, then
+   *     'subject' should be specified here. If complex logic is required to
+   *     build the label, a 'label_callback' should be defined instead (see the
+   *     $label_callback block above for details).
+   *   - uuid (optional): The name of the property that contains the universally
+   *     unique identifier of the entity, which is used to distinctly identify
+   *     an entity across different systems.
+   */
+  public function getKeys();
+
+  /**
+   * Returns a specific entity key.
+   *
+   * @param string $key
+   *   The name of the entity key to return.
+   *
+   * @return string|bool
+   *   The entity key, or FALSE if it does not exist.
+   *
+   * @see self::getKeys()
+   */
+  public function getKey($key);
+
+  /**
+   * Indicates if a given entity key exists.
+   *
+   * @param string $key
+   *   The name of the entity key to check.
+   *
+   * @return bool
+   *   TRUE if a given entity key exists, FALSE otherwise.
+   */
+  public function hasKey($key);
+
+  /**
+   * Indicates whether entities should be statically cached.
+   *
+   * @return bool
+   *   TRUE if static caching should be used; FALSE otherwise.
+   */
+  public function isStaticallyCacheable();
+
+  /**
+   * Indicates whether the rendered output of entities should be cached.
+   *
+   * @return bool
+   */
+  public function isRenderCacheable();
+
+  /**
+   * Indicates if the persistent cache of field data should be used.
+   *
+   * @todo Used by FieldableEntityStorageControllerBase only.
+   *
+   * The persistent cache should usually only be disabled if a higher level
+   * persistent cache is available for the entity type.
+   *
+   * @return bool
+   */
+  public function isFieldDataCacheable();
+
+  /**
+   * Sets the name of the entity type class.
+   *
+   * @param string $class
+   *   The name of the entity type class.
+   *
+   * @return static
+   */
+  public function setClass($class);
+
+  /**
+   * Determines if there is a controller for a given type.
+   *
+   * @param string $controller_type
+   *   The type of controller to check.
+   *
+   * @return bool
+   *   TRUE if a controller of this type exists, FALSE otherwise.
+   */
+  public function hasController($controller_type);
+
+  /**
+   * @param string $controller_type
+   *   The controller type to get.
+   *
+   * @return array|string
+   *   The controllers for a given type.
+   */
+  public function getController($controller_type);
+
+  /**
+   * Returns an array of controllers.
+   *
+   * @return array
+   *   An associative array where the keys are the names of different controller
+   *   types (listed below) and the values are the names of the classes that
+   *   implement that controller:
+   *   - storage: The name of the class used to load the objects. The class must
+   *     implement \Drupal\Core\Entity\EntityStorageControllerInterface.
+   *   - form: An associative array where the keys are the names of the
+   *     different form operations (such as 'create', 'edit', or 'delete') and
+   *     the values are the names of the controller classes for those
+   *     operations. The name of the operation is passed also to the form
+   *     controller's constructor, so that one class can be used for multiple
+   *     entity forms when the forms are similar. The classes must implement
+   *     \Drupal\Core\Entity\EntityFormControllerInterface.
+   *   - list: The name of the class that provides listings of the entities. The
+   *     class must implement \Drupal\Core\Entity\EntityListControllerInterface.
+   *   - render: The name of the class that is used to render the entities. The
+   *     class must implement \Drupal\Core\Entity\EntityViewBuilderInterface.
+   *   - access: The name of the class that is used for access checks. The class
+   *     must implement \Drupal\Core\Entity\EntityAccessControllerInterface.
+   *     Defaults to \Drupal\Core\Entity\EntityAccessController.
+   *   - translation: The name of the controller class that should be used to
+   *     handle the translation process. The class must implement
+   *     \Drupal\content_translation\ContentTranslationControllerInterface.
+   */
+  public function getControllers();
+
+  /**
+   * Sets a form class for a specific operation.
+   *
+   * @param string $operation
+   *   The operation to use this form class for.
+   * @param string $class
+   *   The form class to use for the operation.
+   *
+   * @return static
+   */
+  public function setForm($operation, $class);
+
+  /**
+   * Sets the listing class.
+   *
+   * @param string $class
+   *   The list class to use for the operation.
+   *
+   * @return static
+   */
+  public function setList($class);
+
+  /**
+   * Indicates if the entity type is a subclass of the given class or interface.
+   *
+   * @param string $class
+   *   The class or interface to check.
+   *
+   * @return bool
+   *   TRUE if the entity type is a subclass of the class or interface.
+   */
+  public function isSubclassOf($class);
+
+  /**
+   * Sets the controllers for a given type.
+   *
+   * @param string $controller_type
+   *   The type of controller to set.
+   * @param array|string $value
+   *   The value for a controller type.
+   *
+   * @return static
+   */
+  public function setController($controller_type, $value);
+
+  /**
+   * Returns the name of the default administrative permission.
+   *
+   * The default \Drupal\Core\Entity\EntityAccessController class checks this
+   * permission for all operations in its checkAccess() method. Entities with
+   * more complex permissions can extend this class to do their own access
+   * checks.
+   *
+   * @return string|bool
+   */
+  public function getAdminPermission();
+
+  /**
+   * Returns the permission granularity level.
+   *
+   * The allowed values are respectively "entity_type", "bundle" or FALSE.
+   *
+   * @return string|bool
+   *   Whether a module exposing permissions for the current entity type
+   *   should use entity-type level granularity, bundle level granularity or
+   *   just skip this entity.
+   */
+  public function getPermissionGranularity();
+
+  /**
+   * Get all bundle keys defined on the annotation.
+   *
+   * @return array
+   *   An array describing how the Field API can extract the information it
+   *   needs from the bundle objects for this type (e.g Vocabulary objects for
+   *   terms; not applicable for nodes):
+   *   - bundle: The name of the property that contains the name of the bundle
+   *     object.
+   */
+  public function getBundleKeys();
+
+  /**
+   * Returns a single bundle key.
+   *
+   * @param string $name
+   *   The name of the bundle key.
+   *
+   * @return string|bool
+   *   The value of the bundle key.
+   */
+  public function getBundleKey($name);
+
+  /**
+   * Indicates whether fields can be attached to entities of this type.
+   *
+   * @return bool
+   *   Returns TRUE if the entity type can has fields, otherwise FALSE.
+   */
+  public function isFieldable();
+
+  /**
+   * Returns link templates using the URI template syntax.
+   *
+   * Links are an array of standard link relations to the URI template that
+   * should be used for them. Where possible, link relationships should use
+   * established IANA relationships rather than custom relationships.
+   *
+   * Every entity type should, at minimum, define "canonical", which is the
+   * pattern for URIs to that entity. Even if the entity will have no HTML page
+   * exposed to users it should still have a canonical URI in order to be
+   * compatible with web services. Entities that will be user-editable via an
+   * HTML page must also define an "edit-form" relationship.
+   *
+   * By default, the following placeholders are supported:
+   * - entityType: The machine name of the entity type.
+   * - bundle: The bundle machine name of the entity.
+   * - id: The unique ID of the entity.
+   * - uuid: The UUID of the entity.
+   * - [entityType]: The entity type itself will also be a valid token for the
+   *   ID of the entity. For instance, a placeholder of {node} used on the Node
+   *   class would have the same value as {id}. This is generally preferred
+   *   over "id" for better self-documentation.
+   *
+   * Specific entity types may also expand upon this list by overriding the
+   * Entity::uriPlaceholderReplacements() method.
+   *
+   * @link http://www.iana.org/assignments/link-relations/link-relations.xml @endlink
+   * @link http://tools.ietf.org/html/rfc6570 @endlink
+   *
+   * @return array
+   */
+  public function getLinkTemplates();
+
+  /**
+   * Returns the link template for a given key.
+   *
+   * @param string $key
+   *   The link type.
+   *
+   * @return string|bool
+   *   The route name for this link, or FALSE if it doesn't exist.
+   */
+  public function getLinkTemplate($key);
+
+  /**
+   * Indicates if a link template exists for a given key.
+   *
+   * @param string $key
+   *   The link type.
+   *
+   * @return bool
+   *   TRUE if the link template exists, FALSE otherwise.
+   */
+  public function hasLinkTemplate($key);
+
+  /**
+   * Sets a single link template.
+   *
+   * @param string $key
+   *   The name of a link.
+   * @param string $route_name
+   *   The route name to use for the link.
+   *
+   * @return static
+   */
+  public function setLinkTemplate($key, $route_name);
+
+  /**
+   * Gets the callback for the label of the entity.
+   *
+   * The function takes an entity and optional langcode argument, and returns
+   * the label of the entity. If langcode is omitted, the entity's default
+   * language is used. The entity label is the main string associated with an
+   * entity; for example, the title of a node or the subject of a comment. If
+   * there is an entity object property that defines the label, use the 'label'
+   * element of the 'entity_keys' return value component to provide this
+   * information (see below). If more complex logic is needed to determine the
+   * label of an entity, you can instead specify a callback function here, which
+   * will be called to determine the entity label. See also the
+   * \Drupal\Core\Entity\EntityInterface::label() method, which implements this
+   * logic.
+   *
+   * @return callable|bool
+   *   The callback, or FALSE if none exists.
+   */
+  public function getLabelCallback();
+
+  /**
+   * Sets the label callback.
+   *
+   * @param callable $callback
+   *   A callable that returns the label of the entity.
+   *
+   * @return static
+   */
+  public function setLabelCallback($callback);
+
+  /**
+   * Indicates if a label callback exists.
+   *
+   * @return bool
+   */
+  public function hasLabelCallback();
+
+  /**
+   * Returns the name of the entity type which provides bundles.
+   *
+   * @return string
+   */
+  public function getBundleEntityType();
+
+  /**
+   * Returns the entity type for which this entity provides bundles.
+   *
+   * It can be used by other modules to act accordingly; for example,
+   * the Field UI module uses it to add operation links to manage fields and
+   * displays.
+   *
+   * @return string|bool
+   *   The entity type for which this entity provides bundles, or FALSE if does
+   *   not provide bundles for another entity type.
+   */
+  public function getBundleOf();
+
+  /**
+   * Returns the label for the bundle.
+   *
+   * @return string|bool
+   *   The bundle label, or FALSE if none exists.
+   */
+  public function getBundleLabel();
+
+  /**
+   * Returns the name of the entity's base table.
+   *
+   * @todo Used by DatabaseStorageController only.
+   *
+   * @return string|bool
+   *   The name of the entity's base table, or FALSE if none exists.
+   */
+  public function getBaseTable();
+
+  /**
+   * Indicates whether entities of this type have multilingual support.
+   *
+   * At an entity level, this indicates language support and at a bundle level
+   * this indicates translation support.
+   *
+   * @return bool
+   */
+  public function isTranslatable();
+
+  /**
+   * Returns the config prefix used by the configuration entity type.
+   *
+   * @todo Used for configuration entities only.
+   *
+   * @return string|bool
+   *   The config prefix, or FALSE if not a configuration entity type.
+   */
+  public function getConfigPrefix();
+
+  /**
+   * Returns the name of the entity's revision data table.
+   *
+   * @todo Used by FieldableDatabaseStorageController only.
+   *
+   * @return string|bool
+   *   The name of the entity type's revision data table.
+   */
+  public function getRevisionDataTable();
+
+  /**
+   * Returns the name of the entity's revision table.
+   *
+   * @todo Used by FieldableDatabaseStorageController only.
+   *
+   * @return string|bool
+   *   The name of the entity type's revision table.
+   */
+  public function getRevisionTable();
+
+  /**
+   * Returns the name of the entity's data table.
+   *
+   * @todo Used by FieldableDatabaseStorageController only.
+   *
+   * @return string|bool
+   *   The name of the entity type's data table.
+   */
+  public function getDataTable();
+
+  /**
+   * Returns the human-readable name of the entity type.
+   *
+   * @return string
+   *   The human-readable name of the entity type.
+   */
+  public function getLabel();
+
+  /**
+   * Returns the lowercase form of the human-readable entity type name.
+   *
+   * @return string
+   *   The lowercase form of the human-readable entity type name.
+   */
+  public function getLowercaseLabel();
+
+  /**
+   * Returns a callable that can be used to provide the entity URI.
+   *
+   * This is only called if there is no matching link template for the link
+   * relationship type, and there is no bundle-specific callback provided.
+   *
+   * @return callable|bool
+   *   A valid callback that is passed the entity or FALSE if none is specified.
+   */
+  public function getUriCallback();
+
+  /**
+   * Sets a callable to use to provide the entity URI.
+   *
+   * @param callable $callback
+   *   A callback to use to provide a URI for the entity.
+   *
+   * @return static
+   */
+  public function setUriCallback($callback);
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index c2db6b0..d886224 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -26,9 +26,7 @@ class EntityViewBuilder implements EntityControllerInterface, EntityViewBuilderI
   /**
    * The entity info array.
    *
-   * @var array
-   *
-   * @see entity_get_info()
+   * @var \Drupal\Core\Entity\EntityTypeInterface
    */
   protected $entityInfo;
 
@@ -60,25 +58,26 @@ class EntityViewBuilder implements EntityControllerInterface, EntityViewBuilderI
   /**
    * Constructs a new EntityViewBuilder.
    *
-   * @param string $entity_type
-   *   The entity type.
-   * @param array $entity_info
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
    *   The entity information array.
-   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager service.
    */
-  public function __construct($entity_type, array $entity_info, EntityManager $entity_manager) {
-    $this->entityType = $entity_type;
+  public function __construct(EntityTypeInterface $entity_info, EntityManagerInterface $entity_manager) {
+    $this->entityType = $entity_info->id();
     $this->entityInfo = $entity_info;
     $this->entityManager = $entity_manager;
-    $this->viewModesInfo = entity_get_view_modes($entity_type);
+    $this->viewModesInfo = entity_get_view_modes($this->entityType);
   }
 
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
-    return new static($entity_type, $entity_info, $container->get('entity.manager'));
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
+    return new static(
+      $entity_info,
+      $container->get('entity.manager')
+    );
   }
 
   /**
@@ -140,7 +139,7 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
     // type configuration. The isset() checks below are necessary because
     // 'default' is not an actual view mode.
     $view_mode_is_cacheable = !isset($this->viewModesInfo[$view_mode]) || (isset($this->viewModesInfo[$view_mode]) && $this->viewModesInfo[$view_mode]['cache']);
-    if ($view_mode_is_cacheable && !$entity->isNew() && !isset($entity->in_preview) && $this->entityInfo['render_cache']) {
+    if ($view_mode_is_cacheable && !$entity->isNew() && !isset($entity->in_preview) && $this->entityInfo->isRenderCacheable()) {
       $return['#cache'] = array(
         'keys' => array('entity_view', $this->entityType, $entity->id(), $view_mode),
         'granularity' => DRUPAL_CACHE_PER_ROLE,
diff --git a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php
index c78453c..cb8b4cb 100644
--- a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php
+++ b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Entity;
 
 use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Component\Utility\NestedArray;
@@ -99,9 +100,8 @@ class FieldableDatabaseStorageController extends FieldableEntityStorageControlle
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
       $container->get('database'),
       $container->get('field.info')
@@ -111,49 +111,42 @@ public static function createInstance(ContainerInterface $container, $entity_typ
   /**
    * Constructs a DatabaseStorageController object.
    *
-   * @param string $entity_type
-   *   The entity type for which the instance is created.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Database\Connection $database
    *   The database connection to be used.
    * @param \Drupal\field\FieldInfo $field_info
    *   The field info service.
    */
-  public function __construct($entity_type, array $entity_info, Connection $database, FieldInfo $field_info) {
-    parent::__construct($entity_type, $entity_info);
+  public function __construct(EntityTypeInterface $entity_info, Connection $database, FieldInfo $field_info) {
+    parent::__construct($entity_info);
 
     $this->database = $database;
     $this->fieldInfo = $field_info;
-    $this->bundleKey = !empty($this->entityInfo['entity_keys']['bundle']) ? $this->entityInfo['entity_keys']['bundle'] : FALSE;
-    $this->entityClass = $this->entityInfo['class'];
+    $this->bundleKey = $this->entityInfo->getKey('bundle');
+    $this->entityClass = $this->entityInfo->getClass();
 
     // Check if the entity type supports IDs.
-    if (isset($this->entityInfo['entity_keys']['id'])) {
-      $this->idKey = $this->entityInfo['entity_keys']['id'];
+    if ($this->entityInfo->hasKey('id')) {
+      $this->idKey = $this->entityInfo->getKey('id');
     }
 
     // Check if the entity type supports UUIDs.
-    if (!empty($this->entityInfo['entity_keys']['uuid'])) {
-      $this->uuidKey = $this->entityInfo['entity_keys']['uuid'];
-    }
-    else {
-      $this->uuidKey = FALSE;
-    }
+    $this->uuidKey = $this->entityInfo->getKey('uuid');
 
     // Check if the entity type supports revisions.
-    if (!empty($this->entityInfo['entity_keys']['revision'])) {
-      $this->revisionKey = $this->entityInfo['entity_keys']['revision'];
-      $this->revisionTable = $this->entityInfo['revision_table'];
+    if ($this->entityInfo->hasKey('revision')) {
+      $this->revisionKey = $this->entityInfo->getKey('revision');
+      $this->revisionTable = $this->entityInfo->getRevisionTable();
     }
 
     // Check if the entity type has a dedicated table for fields.
-    if (!empty($this->entityInfo['data_table'])) {
-      $this->dataTable = $this->entityInfo['data_table'];
+    if ($data_table = $this->entityInfo->getDataTable()) {
+      $this->dataTable = $data_table;
       // Entity types having both revision and translation support should always
       // define a revision data table.
-      if ($this->revisionTable && !empty($this->entityInfo['revision_data_table'])) {
-        $this->revisionDataTable = $this->entityInfo['revision_data_table'];
+      if ($this->revisionTable && $revision_data_table = $this->entityInfo->getRevisionDataTable()) {
+        $this->revisionDataTable = $revision_data_table;
       }
     }
   }
@@ -336,10 +329,10 @@ protected function attachPropertyData(array &$entities) {
       $field_definitions = \Drupal::entityManager()->getFieldDefinitions($this->entityType);
       $translations = array();
       if ($this->revisionDataTable) {
-        $data_column_names = array_flip(array_diff(drupal_schema_fields_sql($this->entityInfo['revision_data_table']), drupal_schema_fields_sql($this->entityInfo['base_table'])));
+        $data_column_names = array_flip(array_diff(drupal_schema_fields_sql($this->entityInfo->getRevisionDataTable()), drupal_schema_fields_sql($this->entityInfo->getBaseTable())));
       }
       else {
-        $data_column_names = array_flip(drupal_schema_fields_sql($this->entityInfo['data_table']));
+        $data_column_names = array_flip(drupal_schema_fields_sql($this->entityInfo->getDataTable()));
       }
 
       foreach ($data as $values) {
@@ -459,7 +452,7 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value
    *   A SelectQuery object for loading the entity.
    */
   protected function buildQuery($ids, $revision_id = FALSE) {
-    $query = $this->database->select($this->entityInfo['base_table'], 'base');
+    $query = $this->database->select($this->entityInfo->getBaseTable(), 'base');
 
     $query->addTag($this->entityType . '_load_multiple');
 
@@ -471,11 +464,11 @@ protected function buildQuery($ids, $revision_id = FALSE) {
     }
 
     // Add fields from the {entity} table.
-    $entity_fields = drupal_schema_fields_sql($this->entityInfo['base_table']);
+    $entity_fields = drupal_schema_fields_sql($this->entityInfo->getBaseTable());
 
     if ($this->revisionTable) {
       // Add all fields from the {entity_revision} table.
-      $entity_revision_fields = drupal_map_assoc(drupal_schema_fields_sql($this->entityInfo['revision_table']));
+      $entity_revision_fields = drupal_map_assoc(drupal_schema_fields_sql($this->entityInfo->getRevisionTable()));
       // The ID field is provided by entity, so remove it.
       unset($entity_revision_fields[$this->idKey]);
 
@@ -522,7 +515,7 @@ protected function postLoad(array &$queried_entities) {
     $queried_entities = $this->mapFromStorageRecords($queried_entities);
 
     // Attach field values.
-    if ($this->entityInfo['fieldable']) {
+    if ($this->entityInfo->isFieldable()) {
       $this->loadFieldItems($queried_entities);
     }
 
@@ -548,7 +541,7 @@ public function delete(array $entities) {
       }
       $ids = array_keys($entities);
 
-      $this->database->delete($this->entityInfo['base_table'])
+      $this->database->delete($this->entityInfo->getBaseTable())
         ->condition($this->idKey, $ids)
         ->execute();
 
@@ -615,7 +608,7 @@ public function save(EntityInterface $entity) {
 
       if (!$entity->isNew()) {
         if ($entity->isDefaultRevision()) {
-          $return = drupal_write_record($this->entityInfo['base_table'], $record, $this->idKey);
+          $return = drupal_write_record($this->entityInfo->getBaseTable(), $record, $this->idKey);
         }
         else {
           // @todo, should a different value be returned when saving an entity
@@ -645,7 +638,7 @@ public function save(EntityInterface $entity) {
         // Ensure the entity is still seen as new after assigning it an id,
         // while storing its data.
         $entity->enforceIsNew();
-        $return = drupal_write_record($this->entityInfo['base_table'], $record);
+        $return = drupal_write_record($this->entityInfo->getBaseTable(), $record);
         $entity->{$this->idKey}->value = (string) $record->{$this->idKey};
         if ($this->revisionTable) {
           $entity->setNewRevision();
@@ -691,7 +684,7 @@ public function save(EntityInterface $entity) {
    *   'data_table'.
    */
   protected function savePropertyData(EntityInterface $entity, $table_key = 'data_table') {
-    $table_name = $this->entityInfo[$table_key];
+    $table_name = $this->entityInfo->get($table_key);
     $revision = $table_key != 'data_table';
 
     if (!$revision || !$entity->isNewRevision()) {
@@ -733,11 +726,11 @@ protected function mapToStorageRecord(EntityInterface $entity, $table_key = 'bas
     $record = new \stdClass();
     $values = array();
     $definitions = $entity->getPropertyDefinitions();
-    $schema = drupal_get_schema($this->entityInfo[$table_key]);
+    $schema = drupal_get_schema($this->entityInfo->get($table_key));
     $is_new = $entity->isNew();
 
     $multi_column_fields = array();
-    foreach (drupal_schema_fields_sql($this->entityInfo[$table_key]) as $name) {
+    foreach (drupal_schema_fields_sql($this->entityInfo->get($table_key)) as $name) {
       // Check for fields which store data in multiple columns and process them
       // separately.
       if ($field = strstr($name, '__', TRUE)) {
@@ -814,7 +807,7 @@ protected function saveRevision(EntityInterface $entity) {
     if ($entity->isNewRevision()) {
       drupal_write_record($this->revisionTable, $record);
       if ($entity->isDefaultRevision()) {
-        $this->database->update($this->entityInfo['base_table'])
+        $this->database->update($this->entityInfo->getBaseTable())
           ->fields(array($this->revisionKey => $record->{$this->revisionKey}))
           ->condition($this->idKey, $record->{$this->idKey})
           ->execute();
diff --git a/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php b/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php
index d30c972..9aa3a63 100644
--- a/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php
+++ b/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php
@@ -44,8 +44,7 @@ protected function loadFieldItems(array $entities) {
     // Only the most current revision of non-deleted fields for cacheable entity
     // types can be cached.
     $load_current = $age == static::FIELD_LOAD_CURRENT;
-    $info = entity_get_info($this->entityType);
-    $use_cache = $load_current && $info['field_cache'];
+    $use_cache = $load_current && $this->entityInfo->isFieldDataCacheable();
 
     // Assume all entities will need to be queried. Entities found in the cache
     // will be removed from the list.
@@ -130,7 +129,7 @@ protected function saveFieldItems(EntityInterface $entity, $update = TRUE) {
 
     if ($update) {
       $entity_info = $entity->entityInfo();
-      if ($entity_info['field_cache']) {
+      if ($entity_info->isFieldDataCacheable()) {
         cache('field')->delete('field:' . $entity->entityType() . ':' . $entity->id());
       }
     }
@@ -150,7 +149,7 @@ protected function deleteFieldItems(EntityInterface $entity) {
     $this->doDeleteFieldItems($entity);
 
     $entity_info = $entity->entityInfo();
-    if ($entity_info['field_cache']) {
+    if ($entity_info->isFieldDataCacheable()) {
       cache('field')->delete('field:' . $entity->entityType() . ':' . $entity->id());
     }
   }
diff --git a/core/lib/Drupal/Core/Entity/HtmlEntityFormController.php b/core/lib/Drupal/Core/Entity/HtmlEntityFormController.php
index 4aa7925..44a0bf3 100644
--- a/core/lib/Drupal/Core/Entity/HtmlEntityFormController.php
+++ b/core/lib/Drupal/Core/Entity/HtmlEntityFormController.php
@@ -7,22 +7,39 @@
 
 namespace Drupal\Core\Entity;
 
-use Drupal\Core\Controller\HtmlFormController;
+use Drupal\Core\Controller\ControllerResolverInterface;
+use Drupal\Core\Controller\FormController;
+use Drupal\Core\Form\FormBuilderInterface;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Wrapping controller for entity forms that serve as the main page body.
  */
-class HtmlEntityFormController extends HtmlFormController {
+class HtmlEntityFormController extends FormController {
 
   /**
-   * {@inheritdoc}
+   * The entity manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $manager;
+
+  /**
+   * Constructs a new \Drupal\Core\Routing\Enhancer\FormEnhancer object.
    *
-   * Due to reflection, the argument must be named $_entity_form. The parent
-   * method has $request and $_form, but the parameter must match the route.
+   * @param \Drupal\Core\Controller\ControllerResolverInterface $resolver
+   *   The controller resolver.
+   * @param \Drupal\Core\Entity\EntityManagerInterface $manager
+   *   The entity manager.
+   * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
+   *   The form builder.
+   * @param string $form_definition
+   *   The definition of this form, usually found in $defaults['_entity_form'].
    */
-  public function content(Request $request, $_entity_form) {
-    return parent::content($request, $_entity_form);
+  public function __construct(ControllerResolverInterface $resolver, EntityManagerInterface $manager, FormBuilderInterface $form_builder, $form_definition) {
+    parent::__construct($resolver, $form_builder);
+    $this->manager = $manager;
+    $this->formDefinition = $form_definition;
   }
 
   /**
@@ -46,8 +63,6 @@ public function content(Request $request, $_entity_form) {
    * @endcode
    */
   protected function getFormObject(Request $request, $form_arg) {
-    $manager = $this->container->get('entity.manager');
-
     // If no operation is provided, use 'default'.
     $form_arg .= '.default';
     list ($entity_type, $operation) = explode('.', $form_arg);
@@ -56,10 +71,10 @@ protected function getFormObject(Request $request, $form_arg) {
       $entity = $request->attributes->get($entity_type);
     }
     else {
-      $entity = $manager->getStorageController($entity_type)->create(array());
+      $entity = $this->manager->getStorageController($entity_type)->create(array());
     }
 
-    return $manager->getFormController($entity_type, $operation)->setEntity($entity);
+    return $this->manager->getFormController($entity_type, $operation)->setEntity($entity);
   }
 
 }
diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
index 7828a99..508959c 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
@@ -82,8 +82,8 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
     // Add definitions for each entity type and bundle.
     foreach ($this->entityManager->getDefinitions() as $entity_type => $info) {
       $this->derivatives[$entity_type] = array(
-        'label' => $info['label'],
-        'class' => $info['class'],
+        'label' => $info->getLabel(),
+        'class' => $info->getClass(),
         'constraints' => array('EntityType' => $entity_type),
       ) + $base_plugin_definition;
 
@@ -92,7 +92,7 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
         if ($bundle !== $entity_type) {
           $this->derivatives[$entity_type . ':' . $bundle] = array(
             'label' => $bundle_info['label'],
-            'class' => $info['class'],
+            'class' => $info->getClass(),
             'constraints' => array(
               'EntityType' => $entity_type,
               'Bundle' => $bundle,
diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
index 987a322..f5c84b4 100644
--- a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
+++ b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
@@ -22,9 +22,7 @@ class Query extends QueryBase implements QueryInterface {
   /**
    * Contains the entity info for the entity type of that query.
    *
-   * @var array
-   *
-   * @see \Drupal\Core\Entity\EntityManagerInterface
+   * @var \Drupal\Core\Entity\EntityTypeInterface
    */
   protected $entityInfo;
 
@@ -111,19 +109,18 @@ public function execute() {
   protected function prepare() {
     $entity_type = $this->entityType;
     $this->entityInfo = $this->entityManager->getDefinition($entity_type);
-    if (!isset($this->entityInfo['base_table'])) {
+    if (!$base_table = $this->entityInfo->getBaseTable()) {
       throw new QueryException("No base table, invalid query.");
     }
-    $base_table = $this->entityInfo['base_table'];
     $simple_query = TRUE;
-    if (isset($this->entityInfo['data_table'])) {
+    if ($this->entityInfo->getDataTable()) {
       $simple_query = FALSE;
     }
     $this->sqlQuery = $this->connection->select($base_table, 'base_table', array('conjunction' => $this->conjunction));
     $this->sqlQuery->addMetaData('entity_type', $entity_type);
-    $id_field = $this->entityInfo['entity_keys']['id'];
+    $id_field = $this->entityInfo->getKey('id');
     // Add the key field for fetchAllKeyed().
-    if (empty($this->entityInfo['entity_keys']['revision'])) {
+    if (!$revision_field = $this->entityInfo->getKey('revision')) {
       // When there is no revision support, the key field is the entity key.
       $this->sqlFields["base_table.$id_field"] = array('base_table', $id_field);
       // Now add the value column for fetchAllKeyed(). This is always the
@@ -132,7 +129,6 @@ protected function prepare() {
     }
     else {
       // When there is revision support, the key field is the revision key.
-      $revision_field = $this->entityInfo['entity_keys']['revision'];
       $this->sqlFields["base_table.$revision_field"] = array('base_table', $revision_field);
       // Now add the value column for fetchAllKeyed(). This is always the
       // entity id.
diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
index 8098849..8ec71e1 100644
--- a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
+++ b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
@@ -83,16 +83,16 @@ public function addField($field, $type, $langcode) {
     for ($key = 0; $key <= $count; $key ++) {
       // If there is revision support and only the current revision is being
       // queried then use the revision id. Otherwise, the entity id will do.
-      if (!empty($entity_info['entity_keys']['revision']) && $age == EntityStorageControllerInterface::FIELD_LOAD_CURRENT) {
+      if (($revision_key = $entity_info->getKey('revision')) && $age == EntityStorageControllerInterface::FIELD_LOAD_CURRENT) {
         // This contains the relevant SQL field to be used when joining entity
         // tables.
-        $entity_id_field = $entity_info['entity_keys']['revision'];
+        $entity_id_field = $revision_key;
         // This contains the relevant SQL field to be used when joining field
         // tables.
         $field_id_field = 'revision_id';
       }
       else {
-        $entity_id_field = $entity_info['entity_keys']['id'];
+        $entity_id_field = $entity_info->getKey('id');
         $field_id_field = 'entity_id';
       }
       // This can either be the name of an entity property (non-configurable
@@ -138,8 +138,8 @@ public function addField($field, $type, $langcode) {
             $values = array();
             $field_name = $field->getName();
             // If there are bundles, pick one.
-            if (!empty($entity_info['entity_keys']['bundle'])) {
-              $values[$entity_info['entity_keys']['bundle']] = reset($field_map[$entity_type][$field_name]['bundles']);
+            if ($bundle_key = $entity_info->getKey('bundle')) {
+              $values[$bundle_key] = reset($field_map[$entity_type][$field_name]['bundles']);
             }
             $entity = $entity_manager
               ->getStorageController($entity_type)
@@ -172,11 +172,12 @@ public function addField($field, $type, $langcode) {
         // finds the property first. The data table is preferred, which is why
         // it gets added before the base table.
         $entity_tables = array();
-        if (isset($entity_info['data_table'])) {
+        if ($data_table = $entity_info->getDataTable()) {
           $this->sqlQuery->addMetaData('simple_query', FALSE);
-          $entity_tables[$entity_info['data_table']] = drupal_get_schema($entity_info['data_table']);
+          $entity_tables[$data_table] = drupal_get_schema($data_table);
         }
-        $entity_tables[$entity_info['base_table']] = drupal_get_schema($entity_info['base_table']);
+        $entity_base_table = $entity_info->getBaseTable();
+        $entity_tables[$entity_base_table] = drupal_get_schema($entity_base_table);
         $sql_column = $specifier;
         $table = $this->ensureEntityTable($index_prefix, $specifier, $type, $langcode, $base_table, $entity_id_field, $entity_tables);
       }
@@ -190,9 +191,9 @@ public function addField($field, $type, $langcode) {
           $values = array();
           // If there are bundles, pick one. It does not matter which,
           // properties exist on all bundles.
-          if (!empty($entity_info['entity_keys']['bundle'])) {
+          if ($bundle_key = $entity_info->getKey('bundle')) {
             $bundles = entity_get_bundles($entity_type);
-            $values[$entity_info['entity_keys']['bundle']] = key($bundles);
+            $values[$bundle_key] = key($bundles);
           }
           $entity = $entity_manager
             ->getStorageController($entity_type)
@@ -207,8 +208,8 @@ public function addField($field, $type, $langcode) {
           $entity_type = $propertyDefinitions[$relationship_specifier]->getConstraint('EntityType');
           $entity_info = $entity_manager->getDefinition($entity_type);
           // Add the new entity base table using the table and sql column.
-          $join_condition= '%alias.' . $entity_info['entity_keys']['id'] . " = $table.$sql_column";
-          $base_table = $this->sqlQuery->leftJoin($entity_info['base_table'], NULL, $join_condition);
+          $join_condition= '%alias.' . $entity_info->getKey('id') . " = $table.$sql_column";
+          $base_table = $this->sqlQuery->leftJoin($entity_info->getBaseTable(), NULL, $join_condition);
           $propertyDefinitions = array();
           $key++;
           $index_prefix .= "$next_index_prefix.";
diff --git a/core/lib/Drupal/Core/EventSubscriber/HtmlViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/HtmlViewSubscriber.php
new file mode 100644
index 0000000..f66b604
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/HtmlViewSubscriber.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\EventSubscriber\HtmlViewSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Drupal\Core\Page\HtmlFragment;
+use Drupal\Core\Page\HtmlPage;
+use Drupal\Core\Page\HtmlPageRendererInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
+
+/**
+ * Main subscriber for Html-page responses.
+ */
+class HtmlViewSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The page rendering service.
+   *
+   * @var \Drupal\Core\Page\HtmlPageRendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * Constructs a new HtmlViewSubscriber.
+   *
+   * @param \Drupal\Core\Page\HtmlPageRendererInterface $renderer
+   *   The page rendering service.
+   */
+  public function __construct(HtmlPageRendererInterface $renderer) {
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * Converts an HtmlFragment into an HtmlPage.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event
+   *   The Event to process.
+   */
+  public function onHtmlFragment(GetResponseForControllerResultEvent $event) {
+    $fragment = $event->getControllerResult();
+    if ($fragment instanceof HtmlFragment && !$fragment instanceof HtmlPage) {
+      $page = $this->renderer->render($fragment);
+      $event->setControllerResult($page);
+    }
+  }
+
+  /**
+   * Renders an HtmlPage object to a Response.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event
+   *   The Event to process.
+   */
+  public function onHtmlPage(GetResponseForControllerResultEvent $event) {
+    $page = $event->getControllerResult();
+    if ($page instanceof HtmlPage) {
+      // In case renderPage() returns NULL due to an error cast it to a string
+      // so as to not cause issues with Response. This also allows renderPage
+      // to return an object implementing __toString(), but that is not
+      // recommended.
+      $response = new Response((string) $this->renderer->renderPage($page), $page->getStatusCode());
+      $event->setResponse($response);
+    }
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::VIEW][] = array('onHtmlFragment', 100);
+    $events[KernelEvents::VIEW][] = array('onHtmlPage', 50);
+
+    return $events;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
index e986700..6fb011d 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\EventSubscriber;
 
 use Drupal\Core\Controller\TitleResolverInterface;
+use Drupal\Core\Page\HtmlPage;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\JsonResponse;
@@ -85,28 +86,14 @@ public function onView(GetResponseForControllerResultEvent $event) {
         $event->setResponse(new Response('Not Acceptable', 406));
       }
     }
-    elseif ($request->attributes->get('_legacy')) {
-      // This is an old hook_menu-based subrequest, which means we assume
-      // the body is supposed to be the complete page.
-      $page_result = $event->getControllerResult();
-      if (!is_array($page_result)) {
-        $page_result = array(
-          '#markup' => $page_result,
-        );
-      }
-
-      // If no title was returned fall back to one defined in the route.
-      if (!isset($page_result['#title'])) {
-        $page_result['#title'] = $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT));
-      }
-
-      $event->setResponse(new Response(drupal_render_page($page_result)));
-    }
     else {
       // This is a new-style Symfony-esque subrequest, which means we assume
       // the body is not supposed to be a complete page but just a page
       // fragment.
       $page_result = $event->getControllerResult();
+      if ($page_result instanceof HtmlPage || $page_result instanceof Response) {
+        return $page_result;
+      }
       if (!is_array($page_result)) {
         $page_result = array(
           '#markup' => $page_result,
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
index 655f4fd..40f7a3a 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
@@ -51,7 +51,7 @@ public function getPropertyDefinitions() {
 
     if (!isset(static::$propertyDefinitions[$key])) {
       $target_type_info = \Drupal::entityManager()->getDefinition($target_type);
-      if (is_subclass_of($target_type_info['class'], '\Drupal\Core\Entity\ContentEntityInterface')) {
+      if ($target_type_info->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) {
         // @todo: Lookup the entity type's ID data type and use it here.
         // https://drupal.org/node/2107249
         static::$propertyDefinitions[$key]['target_id'] = DataDefinition::create('integer')
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index 99a2d73..355e560 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -192,10 +192,13 @@ public function getForm($form_arg) {
   /**
    * {@inheritdoc}
    */
-  public function buildForm($form_id, &$form_state) {
+  public function buildForm($form_id, array &$form_state) {
     // Ensure some defaults; if already set they will not be overridden.
     $form_state += $this->getFormStateDefaults();
 
+    // Ensure the form ID is prepared.
+    $form_id = $this->getFormId($form_id, $form_state);
+
     if (!isset($form_state['input'])) {
       $form_state['input'] = $form_state['method'] == 'get' ? $_GET : $_POST;
     }
diff --git a/core/lib/Drupal/Core/Form/FormBuilderInterface.php b/core/lib/Drupal/Core/Form/FormBuilderInterface.php
index 1ccb527..a7205d1 100644
--- a/core/lib/Drupal/Core/Form/FormBuilderInterface.php
+++ b/core/lib/Drupal/Core/Form/FormBuilderInterface.php
@@ -73,7 +73,7 @@ public function getForm($form_arg);
    *   can implement hook_forms(), which maps different $form_id values to the
    *   proper form constructor function. Examples may be found in node_forms(),
    *   and search_forms().
-   * @param $form_state
+   * @param array $form_state
    *   An array which stores information about the form. This is passed as a
    *   reference so that the caller can use it to examine what in the form
    *   changed when the form submission process is complete. Furthermore, it may
@@ -230,7 +230,7 @@ public function getForm($form_arg);
    *
    * @see self::redirectForm()
    */
-  public function buildForm($form_id, &$form_state);
+  public function buildForm($form_id, array &$form_state);
 
   /**
    * Retrieves default values for the $form_state array.
diff --git a/core/lib/Drupal/Core/Menu/LocalTaskDefault.php b/core/lib/Drupal/Core/Menu/LocalTaskDefault.php
index ef8d023..1f5180a 100644
--- a/core/lib/Drupal/Core/Menu/LocalTaskDefault.php
+++ b/core/lib/Drupal/Core/Menu/LocalTaskDefault.php
@@ -92,7 +92,7 @@ public function getTitle() {
   public function getWeight() {
     // By default the weight is 0, or -10 for the root tab.
     if (!isset($this->pluginDefinition['weight'])) {
-      if ($this->pluginDefinition['tab_root_id'] == $this->pluginId) {
+      if ($this->pluginDefinition['base_route'] == $this->pluginDefinition['route_name']) {
         $this->pluginDefinition['weight'] = -10;
       }
       else {
diff --git a/core/lib/Drupal/Core/Menu/LocalTaskDerivativeBase.php b/core/lib/Drupal/Core/Menu/LocalTaskDerivativeBase.php
deleted file mode 100644
index 5e3cacb..0000000
--- a/core/lib/Drupal/Core/Menu/LocalTaskDerivativeBase.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Menu\LocalTaskDerivativeBase.
- */
-
-namespace Drupal\Core\Menu;
-
-use Drupal\Component\Plugin\Derivative\DerivativeBase;
-
-/**
- * Provides a getPluginIdFromRoute method for local task derivatives.
- */
-class LocalTaskDerivativeBase extends DerivativeBase {
-
-  /**
-   * Finds the local task ID of a route given the route name.
-   *
-   * @param string $route_name
-   *   The route name.
-   * @param array $local_tasks
-   *   An array of all local task definitions.
-   *
-   * @return string|null
-   *   Returns the local task ID of the given route or NULL if none is found.
-   */
-  protected function getPluginIdFromRoute($route_name, &$local_tasks) {
-    foreach ($local_tasks as $plugin_id => $local_task) {
-      if ($local_task['route_name'] == $route_name) {
-        return $plugin_id;
-        break;
-      }
-    }
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManager.php b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
index e2a2499..2649aa7 100644
--- a/core/lib/Drupal/Core/Menu/LocalTaskManager.php
+++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
@@ -40,10 +40,10 @@ class LocalTaskManager extends DefaultPluginManager {
     'route_parameters' => array(),
     // The static title for the local task.
     'title' => '',
-    // The plugin ID of the root tab.
-    'tab_root_id' => '',
+    // The route name where the root tab appears.
+    'base_route' => '',
     // The plugin ID of the parent tab (or NULL for the top-level tab).
-    'tab_parent_id' => NULL,
+    'parent_id' => NULL,
     // The weight of the tab.
     'weight' => NULL,
     // The default link options.
@@ -171,7 +171,7 @@ public function getLocalTasksForRoute($route_name) {
     if (!isset($this->instances[$route_name])) {
       $this->instances[$route_name] = array();
       if ($cache = $this->cacheBackend->get($this->cacheKey . ':' . $route_name)) {
-        $tab_root_ids = $cache->data['tab_root_ids'];
+        $base_routes = $cache->data['base_routes'];
         $parents = $cache->data['parents'];
         $children = $cache->data['children'];
       }
@@ -179,47 +179,56 @@ public function getLocalTasksForRoute($route_name) {
         $definitions = $this->getDefinitions();
         // We build the hierarchy by finding all tabs that should
         // appear on the current route.
-        $tab_root_ids = array();
+        $base_routes = array();
         $parents = array();
         $children = array();
         foreach ($definitions as $plugin_id => $task_info) {
+          // Fill in the base_route from the parent to insure consistency.
+          if (!empty($task_info['parent_id']) && !empty($definitions[$task_info['parent_id']])) {
+            $task_info['base_route'] = $definitions[$task_info['parent_id']]['base_route'];
+            // Populate the definitions we use in the next loop. Using a
+            // reference like &$task_info causes bugs.
+            $definitions[$plugin_id]['base_route'] = $definitions[$task_info['parent_id']]['base_route'];
+          }
           if ($route_name == $task_info['route_name']) {
-            $tab_root_ids[$task_info['tab_root_id']] = $task_info['tab_root_id'];
+            if(!empty($task_info['base_route'])) {
+              $base_routes[$task_info['base_route']] = $task_info['base_route'];
+            }
             // Tabs that link to the current route are viable parents
             // and their parent and children should be visible also.
             // @todo - this only works for 2 levels of tabs.
             // instead need to iterate up.
             $parents[$plugin_id] = TRUE;
-            if (!empty($task_info['tab_parent_id'])) {
-              $parents[$task_info['tab_parent_id']] = TRUE;
+            if (!empty($task_info['parent_id'])) {
+              $parents[$task_info['parent_id']] = TRUE;
             }
           }
         }
-        if ($tab_root_ids) {
+        if ($base_routes) {
           // Find all the plugins with the same root and that are at the top
           // level or that have a visible parent.
           foreach ($definitions  as $plugin_id => $task_info) {
-            if (!empty($tab_root_ids[$task_info['tab_root_id']]) && (empty($task_info['tab_parent_id']) || !empty($parents[$task_info['tab_parent_id']]))) {
+            if (!empty($base_routes[$task_info['base_route']]) && (empty($task_info['parent_id']) || !empty($parents[$task_info['parent_id']]))) {
               // Concat '> ' with root ID for the parent of top-level tabs.
-              $parent = empty($task_info['tab_parent_id']) ? '> ' . $task_info['tab_root_id'] : $task_info['tab_parent_id'];
+              $parent = empty($task_info['parent_id']) ? '> ' . $task_info['base_route'] : $task_info['parent_id'];
               $children[$parent][$plugin_id] = $task_info;
             }
           }
         }
         $data = array(
-          'tab_root_ids' => $tab_root_ids,
+          'base_routes' => $base_routes,
           'parents' => $parents,
           'children' => $children,
         );
         $this->cacheBackend->set($this->cacheKey . ':' . $route_name, $data, CacheBackendInterface::CACHE_PERMANENT, $this->cacheTags);
       }
       // Create a plugin instance for each element of the hierarchy.
-      foreach ($tab_root_ids as $root_id) {
+      foreach ($base_routes as $base_route) {
         // Convert the tree keyed by plugin IDs into a simple one with
         // integer depth.  Create instances for each plugin along the way.
         $level = 0;
         // We used this above as the top-level parent array key.
-        $next_parent = '> ' . $root_id;
+        $next_parent = '> ' . $base_route;
         do {
           $parent = $next_parent;
           $next_parent = FALSE;
@@ -283,8 +292,7 @@ public function getTasksBuild($current_route_name) {
           // The plugin may have been set active in getLocalTasksForRoute() if
           // one of its child tabs is the active tab.
           $active = $active || $child->getActive();
-          // @todo It might make sense to use menu link entities instead of
-          //   arrays.
+          // @todo It might make sense to use link render elements instead.
 
           $link = array(
             'title' => $this->getTitle($child),
diff --git a/core/lib/Drupal/Core/Page/DefaultHtmlPageRenderer.php b/core/lib/Drupal/Core/Page/DefaultHtmlPageRenderer.php
new file mode 100644
index 0000000..6543fbc
--- /dev/null
+++ b/core/lib/Drupal/Core/Page/DefaultHtmlPageRenderer.php
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Page\DefaultHtmlPageRenderer
+ */
+
+namespace Drupal\Core\Page;
+
+use Drupal\Core\Language\Language;
+use Drupal\Core\Language\LanguageManager;
+
+/**
+ * Default page rendering engine.
+ */
+class DefaultHtmlPageRenderer implements HtmlPageRendererInterface {
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManager
+   */
+  protected $languageManager;
+
+  /**
+   * Constructs a new DefaultHtmlPageRenderer.
+   *
+   * @param \Drupal\Core\Language\LanguageManager $language_manager
+   *   The language manager service.
+   */
+  public function __construct(LanguageManager $language_manager) {
+    $this->languageManager = $language_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function render(HtmlFragment $fragment, $status_code = 200) {
+    $page = new HtmlPage('', $fragment->getTitle());
+
+    $page_content['main'] = array(
+      '#markup' => $fragment->getContent(),
+    );
+    $page_content['#title'] = $page->getTitle();
+
+    $page_array = drupal_prepare_page($page_content);
+
+    $page = $this->preparePage($page, $page_array);
+
+    $page->setBodyTop(drupal_render($page_array['page_top']));
+    $page->setBodyBottom(drupal_render($page_array['page_bottom']));
+    $page->setContent(drupal_render($page_array));
+
+    $page->setStatusCode($status_code);
+
+    return $page;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function renderPage(HtmlPage $page) {
+    $render = array(
+      '#theme' => 'html',
+      '#page_object' => $page,
+    );
+    return drupal_render($render);
+  }
+
+  /**
+   * Enhances a page object based on a render array.
+   *
+   * @param \Drupal\Core\Page\HtmlPage $page
+   *   The page object to enhance.
+   * @param array $page_array
+   *   The page array to extract onto the page object.
+   *
+   * @return \Drupal\Core\Page\HtmlPage
+   *   The modified page object.
+   */
+  public function preparePage(HtmlPage $page, &$page_array) {
+    // @todo Remove this one drupal_get_title() has been eliminated.
+    if (!$page->hasTitle()) {
+      $title = drupal_get_title();
+      // drupal_set_title() already ensured security, so not letting the
+      // title pass through would cause double escaping.
+      $page->setTitle($title, PASS_THROUGH);
+    }
+
+    $page_array['#page'] = $page;
+
+    // HTML element attributes.
+    $language_interface = $this->languageManager->getLanguage(Language::TYPE_INTERFACE);
+    $html_attributes = $page->getHtmlAttributes();
+    $html_attributes['lang'] = $language_interface->id;
+    $html_attributes['dir'] = $language_interface->direction ? 'rtl' : 'ltr';
+
+    return $page;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Page/HeadElement.php b/core/lib/Drupal/Core/Page/HeadElement.php
new file mode 100644
index 0000000..2c92965
--- /dev/null
+++ b/core/lib/Drupal/Core/Page/HeadElement.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Page\HeadElement.
+ */
+
+namespace Drupal\Core\Page;
+
+use Drupal\Core\Template\Attribute;
+
+/**
+ * This class represents an HTML element that appears in the HEAD tag.
+ */
+class HeadElement {
+
+  /**
+   * An array of attributes for this element.
+   *
+   * @var array
+   */
+  protected $attributes = array();
+
+  /**
+   * The element name.
+   *
+   * Sub-classes should override this value with the name of their element.
+   *
+   * @var string
+   */
+  protected $element = '';
+
+  /**
+   * If this element should be wrapped in <noscript>.
+   *
+   * @var bool
+   */
+  protected $noScript = FALSE;
+
+  /**
+   * Renders this object to an HTML element string.
+   *
+   * @return string
+   */
+  public function __toString() {
+    // Render the attributes via the attribute template class.
+    // @todo Should HeadElement just extend the Attribute classes?
+    $attributes = new Attribute($this->attributes);
+    $rendered = (string) $attributes;
+
+    $string = "<{$this->element}{$rendered} />";
+    if ($this->noScript) {
+      $string = "<noscript>$string</noscript>";
+    }
+    return $string;
+  }
+
+  /**
+   * Sets an attribute on this element.
+   *
+   * @param mixed $key
+   *   The attribute to set.
+   * @param mixed $value
+   *   The value to which to set it.
+   *
+   * @return self
+   *   The invoked object.
+   */
+  public function setAttribute($key, $value) {
+    $this->attributes[$key] = $value;
+    return $this;
+  }
+
+  /**
+   * Sets if this element should be wrapped in <noscript>.
+   *
+   * @param bool $value
+   *   (optional) Whether or not this element should be wrapped in <noscript>.
+   *   Defaults to TRUE.
+   *
+   * @return self
+   *   The element..
+   */
+  public function setNoScript($value = TRUE) {
+    $this->noScript = $value;
+    return $this;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Page/HtmlFragment.php b/core/lib/Drupal/Core/Page/HtmlFragment.php
new file mode 100644
index 0000000..3a50d5e
--- /dev/null
+++ b/core/lib/Drupal/Core/Page/HtmlFragment.php
@@ -0,0 +1,126 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Page\HtmlFragment.
+ */
+
+namespace Drupal\Core\Page;
+
+use Drupal\Component\Utility\String;
+use Drupal\Component\Utility\Xss;
+use Drupal\Core\Utility\Title;
+
+/**
+ * Response object that contains variables for injection into the html template.
+ *
+ * @todo Should we have this conform to an interface?
+ *   https://drupal.org/node/1871596#comment-7134686
+ * @todo Add method replacements for *all* data sourced by html.tpl.php.
+ */
+class HtmlFragment {
+
+  /**
+   * HTML content string.
+   *
+   * @var string
+   */
+  protected $content;
+
+  /**
+   * The title of this HtmlFragment.
+   *
+   * @var string
+   */
+  protected $title = '';
+
+  /**
+   * Constructs a new HtmlFragment.
+   *
+   * @param string $content
+   *   The content for this fragment.
+   */
+  public function __construct($content = '') {
+    $this->content = $content;
+  }
+
+  /**
+   * Sets the response content.
+   *
+   * This should be the bulk of the page content, and will ultimately be placed
+   * within the <body> tag in final HTML output.
+   *
+   * Valid types are strings, numbers, and objects that implement a __toString()
+   * method.
+   *
+   * @param mixed $content
+   *   The content for this fragment.
+   *
+   * @return self
+   *   The fragment.
+   */
+  public function setContent($content) {
+    $this->content = $content;
+    return $this;
+  }
+
+  /**
+   * Gets the main content of this HtmlFragment.
+   *
+   * @return string
+   *   The content for this fragment.
+   */
+  public function getContent() {
+    return $this->content;
+  }
+
+  /**
+   * Sets the title of this HtmlFragment.
+   *
+   * Handling of this title varies depending on what is consuming this
+   * HtmlFragment object. If it's a block, it may only be used as the block's
+   * title; if it's at the page level, it will be used in a number of places,
+   * including the html <head> title.
+   *
+   * @param string $title
+   *   Value to assign to the page title.
+   * @param int $output
+   *   (optional) normally should be left as Title::CHECK_PLAIN. Only set to
+   *   PASS_THROUGH if you have already removed any possibly dangerous code
+   *   from $title using a function like
+   *   \Drupal\Component\Utility\String::checkPlain() or
+   *   \Drupal\Component\Utility\Xss::filterAdmin(). With this flag the string
+   *   will be passed through unchanged.
+   */
+  public function setTitle($title, $output = Title::CHECK_PLAIN) {
+    if ($output == Title::CHECK_PLAIN) {
+      $this->title = String::checkPlain($title);
+    }
+    else if ($output == Title::FILTER_XSS_ADMIN) {
+      $this->title = Xss::filterAdmin($title);
+    }
+    else {
+      $this->title = $title;
+    }
+  }
+
+  /**
+   * Indicates whether or not this HtmlFragment has a title.
+   *
+   * @return bool
+   */
+  public function hasTitle() {
+    return !empty($this->title);
+  }
+
+  /**
+   * Gets the title for this HtmlFragment, if any.
+   *
+   * @return string
+   *   The title.
+   */
+  public function getTitle() {
+    return $this->title;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Page/HtmlPage.php b/core/lib/Drupal/Core/Page/HtmlPage.php
new file mode 100644
index 0000000..6996d7c
--- /dev/null
+++ b/core/lib/Drupal/Core/Page/HtmlPage.php
@@ -0,0 +1,160 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Page\HtmlPage.
+ */
+
+namespace Drupal\Core\Page;
+
+use Drupal\Core\Template\Attribute;
+
+/**
+ * Data object for an HTML page.
+ */
+class HtmlPage extends HtmlFragment {
+
+  /**
+   * Attributes for the HTML element.
+   *
+   * @var \Drupal\Core\Template\Attribute
+   */
+  protected $htmlAttributes;
+
+  /**
+   * Attributes for the BODY element.
+   *
+   * @var \Drupal\Core\Template\Attribute
+   */
+  protected $bodyAttributes;
+
+  /**
+   * Auxiliary content area, above the main content.
+   *
+   * @var string
+   */
+  protected $bodyTop = '';
+
+  /**
+   * Auxiliary content area, below the main content.
+   *
+   * @var string
+   */
+  protected $bodyBottom = '';
+
+  /**
+   * The HTTP status code of this page.
+   *
+   * @var int
+   */
+  protected $statusCode = 200;
+
+  /**
+   * Constructs a new HtmlPage object.
+   *
+   * @param string $content
+   *   (optional) The body content of the page.
+   * @param string $title
+   *   (optional) The title of the page.
+   */
+  public function __construct($content = '', $title = '') {
+    parent::__construct($content);
+
+    $this->title = $title;
+
+    $this->htmlAttributes = new Attribute();
+    $this->bodyAttributes = new Attribute();
+  }
+
+  /**
+   * Returns the HTML attributes for this HTML page.
+   *
+   * @return \Drupal\Core\Template\Attribute
+   */
+  public function getHtmlAttributes() {
+    return $this->htmlAttributes;
+  }
+
+  /**
+   * Returns the HTML attributes for the body element of this page.
+   *
+   * @return \Drupal\Core\Template\Attribute
+   */
+  public function getBodyAttributes() {
+    return $this->bodyAttributes;
+  }
+
+  /**
+   * Sets the top-content of this page.
+   *
+   * @param string $content
+   *   The top-content to set.
+   *
+   * @return self
+   *   The called object.
+   */
+  public function setBodyTop($content) {
+    $this->bodyTop = $content;
+    return $this;
+  }
+
+  /**
+   * Returns the top-content of this page.
+   *
+   * @return string
+   *   The top-content of this page.
+   */
+  public function getBodyTop() {
+    return $this->bodyTop;
+  }
+
+  /**
+   * Sets the bottom-content of this page.
+   *
+   * @param string $content
+   *   The bottom-content to set.
+   *
+   * @return self
+   *   The called object.
+   */
+  public function setBodyBottom($content) {
+    $this->bodyBottom = $content;
+    return $this;
+  }
+
+  /**
+   * Returns the bottom-content of this page.
+   *
+   * @return string
+   *   The bottom-content of this page.
+   */
+  public function getBodyBottom() {
+    return $this->bodyBottom;
+  }
+
+  /**
+   * Sets the HTTP status of this page.
+   *
+   * @param int $status
+   *   The status code to set.
+   *
+   * @return self
+   *   The called object.
+   */
+  public function setStatusCode($status) {
+    $this->statusCode = $status;
+    return $this;
+  }
+
+  /**
+   * Returns the status code of this response.
+   *
+   * @return int
+   *   The status code of this page.
+   */
+  public function getStatusCode() {
+    return $this->statusCode;
+  }
+
+}
+
diff --git a/core/lib/Drupal/Core/Page/HtmlPageRendererInterface.php b/core/lib/Drupal/Core/Page/HtmlPageRendererInterface.php
new file mode 100644
index 0000000..2eb0df1
--- /dev/null
+++ b/core/lib/Drupal/Core/Page/HtmlPageRendererInterface.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Page\HtmlPageRendererInterface
+ */
+
+namespace Drupal\Core\Page;
+
+/**
+ * Interface for HTML Page Renderers.
+ *
+ * An HTML Page Renderer is responsible for translating an HtmlFragment object
+ * into an HtmlPage object, and from a page object into a string.
+ */
+interface HtmlPageRendererInterface {
+
+  /**
+   * Renders an HtmlFragment into an HtmlPage.
+   *
+   * An HtmlFragment represents only a portion of an HTML page, along with
+   * some attached information (assets, metatags, etc.) An HtmlPage represents
+   * an entire page. This method will create an HtmlPage containing the
+   * metadata from the fragment and using the body of the fragment as the main
+   * content region of the page.
+   *
+   * @param \Drupal\Core\Page\HtmlFragment $fragment
+   *   The HTML fragment object to convert up to a page.
+   * @param int $status_code
+   *   (optional) The status code of the page. May be any legal HTTP response
+   *   code. Default is 200 OK.
+   *
+   * @return \Drupal\Core\Page\HtmlPage
+   *   An HtmlPage object derived from the provided fragment.
+   */
+  public function render(HtmlFragment $fragment, $status_code = 200);
+
+  /**
+   * Renders an HtmlPage object to an HTML string.
+   *
+   * @param \Drupal\Core\Page\HtmlPage $page
+   *   The page to render.
+   *
+   * @return string
+   *   A complete HTML page as a string.
+   */
+  public function renderPage(HtmlPage $page);
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php
index ffafdf6..02e96d1 100644
--- a/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php
+++ b/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Plugin\Discovery;
 
+use Drupal\Component\Annotation\AnnotationInterface;
 use Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery as ComponentAnnotatedClassDiscovery;
 
 /**
@@ -71,15 +72,12 @@ protected function getAnnotationReader() {
   /**
    * {@inheritdoc}
    */
-  public function getDefinitions() {
-    $definitions = parent::getDefinitions();
-    foreach ($definitions as &$definition) {
-      // Extract the module name from the class namespace if it's not set.
-      if (!isset($definition['provider'])) {
-        $definition['provider'] = $this->getProviderFromNamespace($definition['class']);
-      }
+  protected function prepareAnnotationDefinition(AnnotationInterface $annotation, $class) {
+    parent::prepareAnnotationDefinition($annotation, $class);
+
+    if (!$annotation->getProvider()) {
+      $annotation->setProvider($this->getProviderFromNamespace($class));
     }
-    return $definitions;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Routing/Enhancer/AjaxEnhancer.php b/core/lib/Drupal/Core/Routing/Enhancer/AjaxEnhancer.php
index 47c5179..4fcd9ea 100644
--- a/core/lib/Drupal/Core/Routing/Enhancer/AjaxEnhancer.php
+++ b/core/lib/Drupal/Core/Routing/Enhancer/AjaxEnhancer.php
@@ -37,9 +37,9 @@ public function __construct(ContentNegotiation $negotiation) {
    * {@inheritdoc}
    */
   public function enhance(array $defaults, Request $request) {
-    if (empty($defaults['_content']) && $this->negotiation->getContentType($request) == 'drupal_ajax') {
+    if (empty($defaults['_content']) && $defaults['_controller'] != 'controller.ajax:content' && $this->negotiation->getContentType($request) == 'drupal_ajax') {
       $defaults['_content'] = isset($defaults['_controller']) ? $defaults['_controller'] : NULL;
-      $defaults['_controller'] = '\Drupal\Core\Controller\AjaxController::content';
+      $defaults['_controller'] = 'controller.ajax:content';
     }
     return $defaults;
   }
diff --git a/core/lib/Drupal/Core/Routing/Enhancer/ContentControllerEnhancer.php b/core/lib/Drupal/Core/Routing/Enhancer/ContentControllerEnhancer.php
index e088d44..3555044 100644
--- a/core/lib/Drupal/Core/Routing/Enhancer/ContentControllerEnhancer.php
+++ b/core/lib/Drupal/Core/Routing/Enhancer/ContentControllerEnhancer.php
@@ -32,6 +32,7 @@ class ContentControllerEnhancer implements RouteEnhancerInterface {
     'drupal_dialog' => 'controller.dialog:dialog',
     'drupal_modal' => 'controller.dialog:modal',
     'html' => 'controller.page:content',
+    'drupal_ajax' => 'controller.ajax:content',
   );
 
   /**
@@ -50,19 +51,17 @@ public function __construct(ContentNegotiation $negotiation) {
   public function enhance(array $defaults, Request $request) {
     // If no controller is set and either _content is set or the request is
     // for a dialog or modal, then enhance.
-    if (empty($defaults['_controller']) &&
-      ($type = $this->negotiation->getContentType($request)) &&
-      (!empty($defaults['_content']) ||
-      in_array($type, array('drupal_dialog', 'drupal_modal')))) {
+    if (empty($defaults['_controller']) && ($type = $this->negotiation->getContentType($request))) {
       if (isset($this->types[$type])) {
         $defaults['_controller'] = $this->types[$type];
       }
     }
+
     // When the dialog attribute is TRUE this is a DialogController sub-request.
     // Route the sub-request to the _content callable.
-    if ($request->attributes->get('dialog') && !empty($defaults['_content'])) {
-      $defaults['_controller'] = $defaults['_content'];
-    }
+    //if ($request->attributes->get('dialog') && !empty($defaults['_content'])) {
+    //  $defaults['_controller'] = $defaults['_content'];
+    //}
     return $defaults;
   }
 }
diff --git a/core/lib/Drupal/Core/Routing/Enhancer/FormEnhancer.php b/core/lib/Drupal/Core/Routing/Enhancer/FormEnhancer.php
index f84bb32..d710665 100644
--- a/core/lib/Drupal/Core/Routing/Enhancer/FormEnhancer.php
+++ b/core/lib/Drupal/Core/Routing/Enhancer/FormEnhancer.php
@@ -7,9 +7,12 @@
 
 namespace Drupal\Core\Routing\Enhancer;
 
+use Drupal\Core\Controller\HtmlFormController;
+use Drupal\Core\Controller\ControllerResolverInterface;
+use Drupal\Core\Form\FormBuilderInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
-use Drupal\Core\ContentNegotiation;
 
 /**
  * Enhances a form route with the appropriate controller.
@@ -17,28 +20,49 @@
 class FormEnhancer implements RouteEnhancerInterface {
 
   /**
-   * Content negotiation library.
+   * The service container.
    *
-   * @var \Drupal\CoreContentNegotiation
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
    */
-  protected $negotiation;
+  protected $container;
+
+  /**
+   * The controller resolver.
+   *
+   * @var \Drupal\Core\Controller\ControllerResolverInterface
+   */
+  protected $resolver;
+
+  /**
+   * The form builder.
+   *
+   * @var \Drupal\Core\Form\FormBuilderInterface
+   */
+  protected $formBuilder;
 
   /**
    * Constructs a new \Drupal\Core\Routing\Enhancer\FormEnhancer object.
    *
-   * @param \Drupal\Core\ContentNegotiation $negotiation
-   *   The Content Negotiation service.
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+   *   The service container.
+   * @param \Drupal\Core\Controller\ControllerResolverInterface $resolver
+   *   The controller resolver.
+   * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
+   *   The form builder.
    */
-  public function __construct(ContentNegotiation $negotiation) {
-    $this->negotiation = $negotiation;
+  public function __construct(ContainerInterface $container, ControllerResolverInterface $resolver, FormBuilderInterface $form_builder) {
+    $this->container = $container;
+    $this->resolver = $resolver;
+    $this->formBuilder = $form_builder;
   }
 
   /**
-   * {@inhertdoc}
+   * {@inheritdoc}
    */
   public function enhance(array $defaults, Request $request) {
-    if (empty($defaults['_controller']) && !empty($defaults['_form']) && $this->negotiation->getContentType($request) === 'html') {
-      $defaults['_controller'] = '\Drupal\Core\Controller\HtmlFormController::content';
+    if (!empty($defaults['_form'])) {
+      $wrapper = new HtmlFormController($this->resolver, $this->container, $defaults['_form'], $this->formBuilder);
+      $defaults['_content'] = array($wrapper, 'getContentResult');
     }
     return $defaults;
   }
diff --git a/core/lib/Drupal/Core/Routing/RouteBuilder.php b/core/lib/Drupal/Core/Routing/RouteBuilder.php
index 8a7cb90..76a826b 100644
--- a/core/lib/Drupal/Core/Routing/RouteBuilder.php
+++ b/core/lib/Drupal/Core/Routing/RouteBuilder.php
@@ -9,8 +9,8 @@
 
 use Drupal\Component\Discovery\YamlDiscovery;
 use Drupal\Core\Controller\ControllerResolverInterface;
+use Symfony\Component\EventDispatcher\Event;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
-use Symfony\Component\Yaml\Parser;
 use Symfony\Component\Routing\RouteCollection;
 use Symfony\Component\Routing\Route;
 
@@ -150,6 +150,7 @@ public function rebuild() {
     $this->dumper->dump(array('provider' => 'dynamic_routes'));
 
     $this->lock->release('router_rebuild');
+    $this->dispatcher->dispatch(RoutingEvents::FINISHED, new Event());
     return TRUE;
   }
 
diff --git a/core/lib/Drupal/Core/Routing/RouteProvider.php b/core/lib/Drupal/Core/Routing/RouteProvider.php
index e90aea3..18dc506 100644
--- a/core/lib/Drupal/Core/Routing/RouteProvider.php
+++ b/core/lib/Drupal/Core/Routing/RouteProvider.php
@@ -246,7 +246,7 @@ protected function getRoutesByPath($path) {
 
     $ancestors = $this->getCandidateOutlines($parts);
 
-    $routes = $this->connection->query("SELECT name, route FROM {" . $this->connection->escapeTable($this->tableName) . "} WHERE pattern_outline IN (:patterns) ORDER BY fit DESC", array(
+    $routes = $this->connection->query("SELECT name, route FROM {" . $this->connection->escapeTable($this->tableName) . "} WHERE pattern_outline IN (:patterns) ORDER BY fit DESC, name ASC", array(
       ':patterns' => $ancestors,
     ))
       ->fetchAllKeyed();
diff --git a/core/lib/Drupal/Core/Routing/RoutingEvents.php b/core/lib/Drupal/Core/Routing/RoutingEvents.php
index 2289e73..b5ee212 100644
--- a/core/lib/Drupal/Core/Routing/RoutingEvents.php
+++ b/core/lib/Drupal/Core/Routing/RoutingEvents.php
@@ -23,4 +23,9 @@
    */
   const ALTER = 'routing.route_alter';
 
+  /**
+   * The FINISHED event is fired when the route building ended.
+   */
+  const FINISHED = 'routing.route_finished';
+
 }
diff --git a/core/lib/Drupal/Core/Template/AttributeValueBase.php b/core/lib/Drupal/Core/Template/AttributeValueBase.php
index 3b082ae..e4236bf 100644
--- a/core/lib/Drupal/Core/Template/AttributeValueBase.php
+++ b/core/lib/Drupal/Core/Template/AttributeValueBase.php
@@ -55,7 +55,9 @@ public function __construct($name, $value) {
    *   The string representation of the attribute.
    */
   public function render() {
-    return String::checkPlain($this->name) . '="' . $this . '"';
+    if (isset($this->value)) {
+      return String::checkPlain($this->name) . '="' . $this . '"';
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php b/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php
index 7da53ef..75afddc 100644
--- a/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php
+++ b/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Theme;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
@@ -15,14 +15,7 @@
 /**
  * Access check for a theme.
  */
-class ThemeAccessCheck implements StaticAccessCheckInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function appliesTo() {
-    return array('_access_theme');
-  }
+class ThemeAccessCheck implements AccessInterface {
 
   /**
    * {@inheritdoc}
diff --git a/core/lib/Drupal/Core/Utility/Title.php b/core/lib/Drupal/Core/Utility/Title.php
index 9642ead..b6380b6 100644
--- a/core/lib/Drupal/Core/Utility/Title.php
+++ b/core/lib/Drupal/Core/Utility/Title.php
@@ -17,4 +17,9 @@ class Title {
    */
   const CHECK_PLAIN = 0;
 
+  /**
+   * For controller titles, for sanitizing via Xss::filterAdmin.
+   */
+  const FILTER_XSS_ADMIN = 1;
+
 }
diff --git a/core/modules/action/action.local_tasks.yml b/core/modules/action/action.local_tasks.yml
index b1d0b61..0ca4413 100644
--- a/core/modules/action/action.local_tasks.yml
+++ b/core/modules/action/action.local_tasks.yml
@@ -1,4 +1,4 @@
 action.admin:
   route_name: action.admin
   title: 'Manage actions'
-  tab_root_id: action.admin
+  base_route: action.admin
diff --git a/core/modules/action/action.module b/core/modules/action/action.module
index 5ecfb63..c7de85c 100644
--- a/core/modules/action/action.module
+++ b/core/modules/action/action.module
@@ -60,9 +60,11 @@ function action_menu() {
  * Implements hook_entity_info().
  */
 function action_entity_info(&$entity_info) {
-  $entity_info['action']['controllers']['form']['add'] = 'Drupal\action\ActionAddFormController';
-  $entity_info['action']['controllers']['form']['edit'] = 'Drupal\action\ActionEditFormController';
-  $entity_info['action']['controllers']['form']['delete'] = 'Drupal\action\Form\ActionDeleteForm';
-  $entity_info['action']['controllers']['list'] = 'Drupal\action\ActionListController';
-  $entity_info['action']['links']['edit-form'] = 'action.admin_configure';
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
+  $entity_info['action']
+    ->setForm('add', 'Drupal\action\ActionAddFormController')
+    ->setForm('edit', 'Drupal\action\ActionEditFormController')
+    ->setForm('delete', 'Drupal\action\Form\ActionDeleteForm')
+    ->setList('Drupal\action\ActionListController')
+    ->setLinkTemplate('edit-form', 'action.admin_configure');
 }
diff --git a/core/modules/action/lib/Drupal/action/ActionListController.php b/core/modules/action/lib/Drupal/action/ActionListController.php
index 405f224..03f7b75 100644
--- a/core/modules/action/lib/Drupal/action/ActionListController.php
+++ b/core/modules/action/lib/Drupal/action/ActionListController.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -35,10 +36,8 @@ class ActionListController extends ConfigEntityListController implements EntityC
   /**
    * Constructs a new ActionListController object.
    *
-   * @param string $entity_type
-   *   The entity type.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The action storage controller.
    * @param \Drupal\Core\Action\ActionManager $action_manager
@@ -46,8 +45,8 @@ class ActionListController extends ConfigEntityListController implements EntityC
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ActionManager $action_manager, ModuleHandlerInterface $module_handler) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage, ActionManager $action_manager, ModuleHandlerInterface $module_handler) {
+    parent::__construct($entity_info, $storage, $module_handler);
 
     $this->actionManager = $action_manager;
   }
@@ -55,11 +54,10 @@ public function __construct($entity_type, array $entity_info, EntityStorageContr
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
-      $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getStorageController($entity_info->id()),
       $container->get('plugin.manager.action'),
       $container->get('module_handler')
     );
diff --git a/core/modules/aggregator/aggregator.local_tasks.yml b/core/modules/aggregator/aggregator.local_tasks.yml
index ceb7b45..b74ecf3 100644
--- a/core/modules/aggregator/aggregator.local_tasks.yml
+++ b/core/modules/aggregator/aggregator.local_tasks.yml
@@ -1,19 +1,19 @@
 aggregator.admin_overview:
   route_name: aggregator.admin_overview
   title: 'List'
-  tab_root_id: aggregator.admin_overview
+  base_route: aggregator.admin_overview
 aggregator.admin_settings:
   route_name: aggregator.admin_settings
   title: 'Settings'
   weight: 100
-  tab_root_id: aggregator.admin_overview
+  base_route: aggregator.admin_overview
 
 aggregator.feed_view:
   route_name: aggregator.feed_view
-  tab_root_id: aggregator.feed_view
+  base_route: aggregator.feed_view
   title: View
 aggregator.feed_configure:
   route_name: aggregator.feed_configure
-  tab_root_id: aggregator.feed_view
+  base_route: aggregator.feed_view
   title: 'Configure'
   weight: 10
diff --git a/core/modules/aggregator/aggregator.routing.yml b/core/modules/aggregator/aggregator.routing.yml
index 007a010..33ac34d 100644
--- a/core/modules/aggregator/aggregator.routing.yml
+++ b/core/modules/aggregator/aggregator.routing.yml
@@ -82,7 +82,7 @@ aggregator.feed_view:
 aggregator.page_last:
   path: '/aggregator'
   defaults:
-    _controller: '\Drupal\aggregator\Controller\AggregatorController::pageLast'
+    _content: '\Drupal\aggregator\Controller\AggregatorController::pageLast'
     _title: 'Feed aggregator'
   requirements:
     _permission: 'access news feeds'
diff --git a/core/modules/aggregator/aggregator.services.yml b/core/modules/aggregator/aggregator.services.yml
index e486b54..c00b2ac 100644
--- a/core/modules/aggregator/aggregator.services.yml
+++ b/core/modules/aggregator/aggregator.services.yml
@@ -8,3 +8,11 @@ services:
   plugin.manager.aggregator.processor:
     class: Drupal\aggregator\Plugin\AggregatorPluginManager
     arguments: [processor, '@container.namespaces', '@cache.cache', '@language_manager']
+  access_check.aggregator.categories:
+    class: Drupal\aggregator\Access\CategoriesAccessCheck
+    arguments: ['@database']
+    tags:
+      - { name: access_check, applies_to: _access_aggregator_categories }
+  aggregator.category.storage:
+    class: Drupal\aggregator\CategoryStorageController
+    arguments: ['@database']
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Access/CategoriesAccessCheck.php b/core/modules/aggregator/lib/Drupal/aggregator/Access/CategoriesAccessCheck.php
new file mode 100644
index 0000000..791eddd
--- /dev/null
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Access/CategoriesAccessCheck.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\aggregator\Access\CategoriesAccess.
+ */
+
+namespace Drupal\aggregator\Access;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Provides an access check for aggregator categories routes.
+ */
+class CategoriesAccessCheck implements AccessInterface {
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $database;
+
+  /**
+   * Constructs a CategoriesAccessCheck object.
+   *
+   * @param \Drupal\Core\Database\Connection
+   *   The database connection.
+   */
+  public function __construct(Connection $database) {
+    $this->database = $database;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request, AccountInterface $account) {
+    return $account->hasPermission('access news feeds') && (bool) $this->database->queryRange('SELECT 1 FROM {aggregator_category}', 0, 1)->fetchField() ? static::ALLOW : static::DENY;
+  }
+
+}
diff --git a/core/modules/block/block.local_tasks.yml b/core/modules/block/block.local_tasks.yml
index 3fdea89..3d770ed 100644
--- a/core/modules/block/block.local_tasks.yml
+++ b/core/modules/block/block.local_tasks.yml
@@ -1,16 +1,15 @@
 block.admin_edit:
   title: 'Configure block'
   route_name: block.admin_edit
-  tab_root_id: block.admin_edit
+  base_route: block.admin_edit
 
 # Per theme block layout pages.
 block.admin_display:
   title: 'Block Layout'
   route_name: block.admin_display
-  tab_root_id: block.admin_display
+  base_route: block.admin_display
 block.admin_display_theme:
   title: 'Block Layout'
   route_name: block.admin_display_theme
-  tab_root_id: block.admin_display
-  tab_parent_id: block.admin_display
+  parent_id: block.admin_display
   derivative: 'Drupal\block\Plugin\Derivative\ThemeLocalTask'
diff --git a/core/modules/block/block.routing.yml b/core/modules/block/block.routing.yml
index 9e0226d..e7bbf22 100644
--- a/core/modules/block/block.routing.yml
+++ b/core/modules/block/block.routing.yml
@@ -32,7 +32,7 @@ block.admin_display:
 block.admin_display_theme:
   path: 'admin/structure/block/list/{theme}'
   defaults:
-    _controller: '\Drupal\block\Controller\BlockListController::listing'
+    _content: '\Drupal\block\Controller\BlockListController::listing'
   requirements:
     _access_theme: 'TRUE'
     _permission: 'administer blocks'
diff --git a/core/modules/block/custom_block/custom_block.local_tasks.yml b/core/modules/block/custom_block/custom_block.local_tasks.yml
index 9bee9a4..041abb7 100644
--- a/core/modules/block/custom_block/custom_block.local_tasks.yml
+++ b/core/modules/block/custom_block/custom_block.local_tasks.yml
@@ -1,29 +1,27 @@
 custom_block.list:
   title: 'Custom block library'
   route_name: custom_block.list
-  tab_root_id: block.admin_display
+  base_route: block.admin_display
 custom_block.list_sub:
   title: Blocks
   route_name: custom_block.list
-  tab_root_id: block.admin_display
-  tab_parent_id: custom_block.list
+  parent_id: custom_block.list
 custom_block.type_list:
   title: Types
   route_name: custom_block.type_list
-  tab_root_id: block.admin_display
-  tab_parent_id: custom_block.list
+  parent_id: custom_block.list
 
 custom_block.edit:
   title: Edit
   route_name: custom_block.edit
-  tab_root_id: custom_block.edit
+  base_route: custom_block.edit
 custom_block.delete:
   title: Delete
   route_name: custom_block.delete
-  tab_root_id: custom_block.edit
+  base_route: custom_block.edit
 
 # Default tab for custom block type editing.
 custom_block.type_edit:
   title: 'Edit'
   route_name: custom_block.type_edit
-  tab_root_id: custom_block.type_edit
+  base_route: custom_block.type_edit
diff --git a/core/modules/block/custom_block/custom_block.module b/core/modules/block/custom_block/custom_block.module
index 4cf2e68..21aa308 100644
--- a/core/modules/block/custom_block/custom_block.module
+++ b/core/modules/block/custom_block/custom_block.module
@@ -125,10 +125,13 @@ function custom_block_load($id) {
 /**
  * Implements hook_entity_info_alter().
  */
-function custom_block_entity_info_alter(&$types) {
+function custom_block_entity_info_alter(&$entity_info) {
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
   // Add a translation handler for fields if the language module is enabled.
   if (\Drupal::moduleHandler()->moduleExists('language')) {
-    $types['custom_block']['translation']['custom_block'] = TRUE;
+    $translation = $entity_info['custom_block']->get('translation');
+    $translation['custom_block'] = TRUE;
+    $entity_info['custom_block']->set('translation', $translation);
   }
 }
 
diff --git a/core/modules/block/lib/Drupal/block/BlockAccessController.php b/core/modules/block/lib/Drupal/block/BlockAccessController.php
index a82de8b..315e4f6 100644
--- a/core/modules/block/lib/Drupal/block/BlockAccessController.php
+++ b/core/modules/block/lib/Drupal/block/BlockAccessController.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Entity\EntityAccessController;
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Path\AliasManagerInterface;
 use Drupal\Component\Utility\Unicode;
@@ -30,24 +31,21 @@ class BlockAccessController extends EntityAccessController implements EntityCont
   /**
    * Constructs a BlockAccessController object.
    *
-   * @param string $entity_type
-   *   The entity type of the access controller instance.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Path\AliasManagerInterface $alias_manager
    *   The alias manager.
    */
-  public function __construct($entity_type, array $entity_info, AliasManagerInterface $alias_manager) {
-    parent::__construct($entity_type, $entity_info);
+  public function __construct(EntityTypeInterface $entity_info, AliasManagerInterface $alias_manager) {
+    parent::__construct($entity_info);
     $this->aliasManager = $alias_manager;
   }
 
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
       $container->get('path.alias_manager')
     );
diff --git a/core/modules/block/lib/Drupal/block/BlockListController.php b/core/modules/block/lib/Drupal/block/BlockListController.php
index 6db9219..8547ba4 100644
--- a/core/modules/block/lib/Drupal/block/BlockListController.php
+++ b/core/modules/block/lib/Drupal/block/BlockListController.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\FormInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -55,10 +56,8 @@ class BlockListController extends ConfigEntityListController implements FormInte
   /**
    * Constructs a new BlockListController object.
    *
-   * @param string $entity_type
-   *   The type of entity to be listed.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
@@ -66,8 +65,8 @@ class BlockListController extends ConfigEntityListController implements FormInte
    * @param \Drupal\Component\Plugin\PluginManagerInterface $block_manager
    *   The block manager.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, PluginManagerInterface $block_manager) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, PluginManagerInterface $block_manager) {
+    parent::__construct($entity_info, $storage, $module_handler);
 
     $this->blockManager = $block_manager;
   }
@@ -75,11 +74,10 @@ public function __construct($entity_type, array $entity_info, EntityStorageContr
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
-      $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getStorageController($entity_info->id()),
       $container->get('module_handler'),
       $container->get('plugin.manager.block')
     );
@@ -102,7 +100,7 @@ public function load() {
     $entities = _block_rehash($this->theme);
 
     // Sort the blocks using \Drupal\block\Entity\Block::sort().
-    uasort($entities, array($this->entityInfo['class'], 'sort'));
+    uasort($entities, array($this->entityInfo->getClass(), 'sort'));
     return $entities;
   }
 
diff --git a/core/modules/block/lib/Drupal/block/Plugin/Derivative/ThemeLocalTask.php b/core/modules/block/lib/Drupal/block/Plugin/Derivative/ThemeLocalTask.php
index 77a2235..9988813 100644
--- a/core/modules/block/lib/Drupal/block/Plugin/Derivative/ThemeLocalTask.php
+++ b/core/modules/block/lib/Drupal/block/Plugin/Derivative/ThemeLocalTask.php
@@ -56,8 +56,8 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
       // Default task!
       if ($default_theme == $theme_name) {
         $this->derivatives[$theme_name]['route_name'] = 'block.admin_display';
-        // Emulate default logic because without the base plugin id we can't set the
-        // change the tab_root_id.
+        // Emulate default logic because without the base plugin id we can't
+        // change the base_route.
         $this->derivatives[$theme_name]['weight'] = -10;
 
         unset($this->derivatives[$theme_name]['route_parameters']);
diff --git a/core/modules/block/lib/Drupal/block/Tests/Views/DisplayBlockTest.php b/core/modules/block/lib/Drupal/block/Tests/Views/DisplayBlockTest.php
index fd9d254..1cee4b8 100644
--- a/core/modules/block/lib/Drupal/block/Tests/Views/DisplayBlockTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/Views/DisplayBlockTest.php
@@ -175,6 +175,19 @@ protected function testDeleteBlockDisplay() {
   }
 
   /**
+   * Tests views block plugin definitions.
+   */
+  public function testViewsBlockPlugins() {
+    // Ensures that the cache setting gets to the block settings.
+    $instance = $this->container->get('plugin.manager.block')->createInstance('views_block:test_view_block2-block_2');
+    $configuration = $instance->getConfiguration();
+    $this->assertEqual($configuration['cache'], DRUPAL_NO_CACHE);
+    $instance = $this->container->get('plugin.manager.block')->createInstance('views_block:test_view_block2-block_3');
+    $configuration = $instance->getConfiguration();
+    $this->assertEqual($configuration['cache'], DRUPAL_CACHE_PER_USER);
+  }
+
+  /**
    * Test the block form for a Views block.
    */
   public function testViewsBlockForm() {
diff --git a/core/modules/block/tests/modules/block_test_views/test_views/views.view.test_view_block2.yml b/core/modules/block/tests/modules/block_test_views/test_views/views.view.test_view_block2.yml
index e693732..9b71048 100644
--- a/core/modules/block/tests/modules/block_test_views/test_views/views.view.test_view_block2.yml
+++ b/core/modules/block/tests/modules/block_test_views/test_views/views.view.test_view_block2.yml
@@ -51,6 +51,16 @@ display:
       field:
         title:
           link_to_node: '1'
+  block_3:
+    display_plugin: block
+    id: block_3
+    display_title: Block
+    position: ''
+    display_options:
+      block_caching: '2'
+      field:
+        title:
+          link_to_node: '1'
 label: test_view_block2
 module: views
 id: test_view_block2
diff --git a/core/modules/book/book.local_tasks.yml b/core/modules/book/book.local_tasks.yml
index f0f8464..796c5fd 100644
--- a/core/modules/book/book.local_tasks.yml
+++ b/core/modules/book/book.local_tasks.yml
@@ -1,15 +1,15 @@
 book.admin:
   route_name: book.admin
   title: 'List'
-  tab_root_id: book.admin
+  base_route: book.admin
 book.settings:
   route_name: book.settings
   title: 'Settings'
-  tab_root_id: book.admin
+  base_route: book.admin
   weight: 100
 
 book.outline:
   route_name: book.outline
-  tab_root_id: node.view
+  base_route: node.view
   title: Outline
   weight: 2
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index ec8cf4c..f49a3e2 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -115,7 +115,8 @@ function book_permission() {
  * Implements hook_entity_info().
  */
 function book_entity_info(&$entity_info) {
-  $entity_info['node']['controllers']['form']['book_outline'] = '\Drupal\book\Form\BookOutlineForm';
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
+  $entity_info['node']->setForm('book_outline', 'Drupal\book\Form\BookOutlineForm');
 }
 
 /**
diff --git a/core/modules/book/book.services.yml b/core/modules/book/book.services.yml
index fa7c66c..daa995a 100644
--- a/core/modules/book/book.services.yml
+++ b/core/modules/book/book.services.yml
@@ -15,4 +15,4 @@ services:
     class: Drupal\book\Access\BookNodeIsRemovableAccessCheck
     arguments: ['@book.manager']
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _access_book_removable }
diff --git a/core/modules/book/lib/Drupal/book/Access/BookNodeIsRemovableAccessCheck.php b/core/modules/book/lib/Drupal/book/Access/BookNodeIsRemovableAccessCheck.php
index c80ae27..7b12219 100644
--- a/core/modules/book/lib/Drupal/book/Access/BookNodeIsRemovableAccessCheck.php
+++ b/core/modules/book/lib/Drupal/book/Access/BookNodeIsRemovableAccessCheck.php
@@ -8,7 +8,7 @@
 namespace Drupal\book\Access;
 
 use Drupal\book\BookManager;
-use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -16,7 +16,7 @@
 /**
  * Determines whether the requested node can be removed from its book.
  */
-class BookNodeIsRemovableAccessCheck implements StaticAccessCheckInterface {
+class BookNodeIsRemovableAccessCheck implements AccessInterface {
 
   /**
    * Book Manager Service.
@@ -38,13 +38,6 @@ public function __construct(BookManager $book_manager) {
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    return array('_access_book_removable');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     $node = $request->attributes->get('node');
     if (!empty($node)) {
diff --git a/core/modules/breakpoint/breakpoint.module b/core/modules/breakpoint/breakpoint.module
index a2450f9..9306fec 100644
--- a/core/modules/breakpoint/breakpoint.module
+++ b/core/modules/breakpoint/breakpoint.module
@@ -75,11 +75,12 @@ function breakpoint_modules_uninstalled($modules) {
  */
 function _breakpoint_delete_breakpoints($list, $source_type) {
   $ids = config_get_storage_names_with_prefix('breakpoint.breakpoint_group.' . $source_type . '.');
-  $entity_info = \Drupal::service('entity.manager')->getDefinition('breakpoint_group');
+  $entity_manager = \Drupal::entityManager();
+  $entity_info = $entity_manager->getDefinition('breakpoint_group');
 
   // Remove the breakpoint.breakpoint part of the breakpoint identifier.
   foreach ($ids as &$id) {
-    $id = ConfigStorageController::getIDFromConfigName($id, $entity_info['config_prefix']);
+    $id = ConfigStorageController::getIDFromConfigName($id, $entity_info->getConfigPrefix());
   }
   $breakpoint_groups = entity_load_multiple('breakpoint_group', $ids);
 
@@ -90,11 +91,11 @@ function _breakpoint_delete_breakpoints($list, $source_type) {
 
       // Get all breakpoints defined by this theme/module.
       $breakpoint_ids = \Drupal::service('config.storage')->listAll('breakpoint.breakpoint.' . $source_type . '.' . $breakpoint_group->id() . '.');
-      $entity_info = \Drupal::service('entity.manager')->getDefinition('breakpoint');
+      $entity_info = $entity_manager->getDefinition('breakpoint');
 
       // Remove the breakpoint.breakpoint part of the breakpoint identifier.
       foreach ($breakpoint_ids as &$breakpoint_id) {
-        $breakpoint_id = ConfigStorageController::getIDFromConfigName($breakpoint_id, $entity_info['config_prefix']);
+        $breakpoint_id = ConfigStorageController::getIDFromConfigName($breakpoint_id, $entity_info->getConfigPrefix());
       }
       $breakpoints = entity_load_multiple('breakpoint', $breakpoint_ids);
 
diff --git a/core/modules/comment/comment.local_tasks.yml b/core/modules/comment/comment.local_tasks.yml
index baba52c..091321b 100644
--- a/core/modules/comment/comment.local_tasks.yml
+++ b/core/modules/comment/comment.local_tasks.yml
@@ -1,33 +1,31 @@
 comment.permalink_tab:
   route_name: comment.permalink
   title: 'View comment'
-  tab_root_id: comment.permalink_tab
+  base_route: comment.permalink
 comment.edit_page_tab:
   route_name: comment.edit_page
   title: 'Edit'
-  tab_root_id: comment.permalink_tab
+  base_route: comment.permalink
   weight: 0
 comment.confirm_delete_tab:
   route_name: comment.confirm_delete
   title: 'Delete'
-  tab_root_id: comment.permalink_tab
+  base_route: comment.permalink
   weight: 10
 
 comment.admin:
   title: Comments
   route_name: comment.admin
-  tab_root_id: node.content_overview
+  base_route: node.content_overview
 
 comment.admin_new:
   title: 'Published comments'
   route_name: comment.admin
-  tab_root_id: node.content_overview
-  tab_parent_id: comment.admin
+  parent_id: comment.admin
 
 comment.admin_approval:
   title: 'Unapproved comments'
   route_name: comment.admin_approval
   class: Drupal\comment\Plugin\Menu\LocalTask\UnapprovedComments
-  tab_root_id: node.content_overview
-  tab_parent_id: comment.admin
+  parent_id: comment.admin
   weight: 1
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 76120b2..daa2399 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -11,7 +11,6 @@
  */
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityChangedInterface;
 use Drupal\comment\CommentInterface;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\field\FieldInstanceInterface;
@@ -890,13 +889,9 @@ function comment_entity_load($entities, $entity_type) {
     // Do not query database when entity has no comment fields.
     return;
   }
-  // Load comment information from the database and update the entity's comment
-  // statistics properties, which are defined on each CommentItem field.
-  $result = db_select('comment_entity_statistics', 'ces')
-    ->fields('ces')
-    ->condition('ces.entity_id', array_keys($entities))
-    ->condition('ces.entity_type', $entity_type)
-    ->execute();
+  // Load comment information from the database and update the entity's
+  // comment statistics properties, which are defined on each CommentItem field.
+  $result = \Drupal::service('comment.statistics')->read($entities, $entity_type);
   foreach ($result as $record) {
     $parts = explode('__', $record->field_id, 2);
     list(, $field_name) = $parts;
@@ -922,48 +917,7 @@ function comment_entity_insert(EntityInterface $entity) {
   // maintenance of the {comment_entity_statistics} table.
   if (\Drupal::state()->get('comment.maintain_entity_statistics') &&
     $fields = \Drupal::service('comment.manager')->getFields($entity->entityType())) {
-    $query = db_insert('comment_entity_statistics')
-     ->fields(array(
-      'entity_id',
-      'entity_type',
-      'field_id',
-      'cid',
-      'last_comment_timestamp',
-      'last_comment_name',
-      'last_comment_uid',
-      'comment_count'
-    ));
-    foreach ($fields as $field_name => $detail) {
-      // Skip fields that entity does not have.
-      if (!$entity->hasField($field_name)) {
-        continue;
-      }
-      // There is at least one comment field, the query needs to be executed.
-      // @todo Use $entity->getAuthorId() after https://drupal.org/node/2078387
-      if ($entity->hasField('uid')) {
-        $last_comment_uid = $entity->get('uid')->value;
-      }
-      else {
-        // Default to current user when entity does not have a uid property.
-        $last_comment_uid = \Drupal::currentUser()->id();
-      }
-      // Default to REQUEST_TIME when entity does not have a changed property.
-      $last_comment_timestamp = REQUEST_TIME;
-      if ($entity instanceof EntityChangedInterface) {
-        $last_comment_timestamp = $entity->getChangedTime();
-      }
-      $query->values(array(
-        'entity_id' => $entity->id(),
-        'entity_type' => $entity->entityType(),
-        'field_id' => $entity->entityType() . '__' . $field_name,
-        'cid' => 0,
-        'last_comment_timestamp' => $last_comment_timestamp,
-        'last_comment_name' => NULL,
-        'last_comment_uid' => $last_comment_uid,
-        'comment_count' => 0,
-      ));
-    }
-    $query->execute();
+    \Drupal::service('comment.statistics')->initializeRecord($entity, $fields);
   }
 }
 
@@ -978,10 +932,7 @@ function comment_entity_predelete(EntityInterface $entity) {
     ->execute()
     ->fetchCol();
   entity_delete_multiple('comment', $cids);
-  db_delete('comment_entity_statistics')
-    ->condition('entity_id', $entity->id())
-    ->condition('entity_type', $entity->entityType())
-    ->execute();
+  \Drupal::service('comment.statistics')->delete($entity);
 }
 
 /**
@@ -1039,8 +990,9 @@ function comment_node_update_index(EntityInterface $node, $langcode) {
  * Implements hook_update_index().
  */
 function comment_update_index() {
-  // Store the maximum possible comments per thread (used for ranking by reply count)
-  \Drupal::state()->set('comment.node_comment_statistics_scale', 1.0 / max(1, db_query('SELECT MAX(comment_count) FROM {comment_entity_statistics}')->fetchField()));
+  // Store the maximum possible comments per thread (used for ranking by
+  // reply count).
+  \Drupal::state()->set('comment.node_comment_statistics_scale', 1.0 / max(1, \Drupal::service('comment.statistics')->maximumCount()));
 }
 
 /**
@@ -1153,7 +1105,7 @@ function comment_load($cid, $reset = FALSE) {
  * @param $timestamp
  *   Time to count from (defaults to time of last user access to node).
  *
- * @return int|FALSE
+ * @return int|false
  *   The number of new comments or FALSE if the user is not logged in.
  */
 function comment_num_new($entity_id, $entity_type, $field_name = NULL, $timestamp = 0) {
@@ -1575,19 +1527,6 @@ function template_preprocess_comment_wrapper(&$variables) {
 }
 
 /**
- * Returns an array of viewing modes for comment listings.
- *
- * We can't use a global variable array because the locale system
- * is not initialized yet when the Comment module is loaded.
- */
-function _comment_get_modes() {
-  return array(
-    COMMENT_MODE_FLAT => t('Flat list'),
-    COMMENT_MODE_THREADED => t('Threaded list')
-  );
-}
-
-/**
  * Returns an array of "comments per page" values that users can select from.
  */
 function _comment_per_page() {
@@ -1627,22 +1566,7 @@ function comment_alphadecimal_to_int($c = '00') {
  * Implements hook_ranking().
  */
 function comment_ranking() {
-  return array(
-    'comments' => array(
-      'title' => t('Number of comments'),
-      'join' => array(
-        'type' => 'LEFT',
-        'table' => 'comment_entity_statistics',
-        'alias' => 'ces',
-        // Default to comment field as this is the most common use case for
-        // nodes.
-        'on' => "ces.entity_id = i.sid AND ces.entity_type = 'node' AND ces.field_id = 'node__comment'",
-      ),
-      // Inverse law that maps the highest reply count on the site to 1 and 0 to 0.
-      'score' => '2.0 - 2.0 / (1.0 + ces.comment_count * CAST(:scale AS DECIMAL))',
-      'arguments' => array(':scale' => \Drupal::state()->get('comment.node_comment_statistics_scale') ?: 0),
-    ),
-  );
+  return \Drupal::service('comment.statistics')->rankingInfo();
 }
 
 /**
diff --git a/core/modules/comment/comment.services.yml b/core/modules/comment/comment.services.yml
index 084f9b8..94ae256 100644
--- a/core/modules/comment/comment.services.yml
+++ b/core/modules/comment/comment.services.yml
@@ -9,6 +9,10 @@ services:
     class: Drupal\comment\CommentManager
     arguments: ['@field.info', '@entity.manager']
 
+  comment.statistics:
+    class: Drupal\comment\CommentStatistics
+    arguments: ['@database', '@current_user', '@entity.manager', '@state']
+
   comment.route_enhancer:
       class: Drupal\comment\Routing\CommentBundleEnhancer
       arguments: ['@entity.manager']
diff --git a/core/modules/comment/comment.views.inc b/core/modules/comment/comment.views.inc
index ed8f85a..63b9b48 100644
--- a/core/modules/comment/comment.views.inc
+++ b/core/modules/comment/comment.views.inc
@@ -353,19 +353,19 @@ function comment_views_data() {
 
   // Provide a relationship for each entity type except comment.
   foreach ($entities_info as $type => $entity_info) {
-    if ($type == 'comment' || empty($entity_info['fieldable']) || !isset($entity_info['base_table'])) {
+    if ($type == 'comment' || !$entity_info->isFieldable() || !$entity_info->getBaseTable()) {
       continue;
     }
     if ($fields = \Drupal::service('comment.manager')->getFields($type)) {
       $data['comment'][$type] = array(
         'relationship' => array(
-          'title' => $entity_info['label'],
-          'help' => t('The @entity_type to which the comment is a reply to.', array('@entity_type' => $entity_info['label'])),
-          'base' => $entity_info['base_table'],
-          'base field' => $entity_info['entity_keys']['id'],
+          'title' => $entity_info->getLabel(),
+          'help' => t('The @entity_type to which the comment is a reply to.', array('@entity_type' => $entity_info->getLabel())),
+          'base' => $entity_info->getBaseTable(),
+          'base field' => $entity_info->getKey('id'),
           'relationship field' => 'entity_id',
           'id' => 'standard',
-          'label' => $entity_info['label'],
+          'label' => $entity_info->getLabel(),
           'extra' => array(
             array(
               'field' => 'entity_type',
@@ -432,7 +432,7 @@ function comment_views_data() {
 
   // Provide a relationship for each entity type except comment.
   foreach ($entities_info as $type => $entity_info) {
-    if ($type == 'comment' || empty($entity_info['fieldable']) || !isset($entity_info['base_table'])) {
+    if ($type == 'comment' || !$entity_info->isFieldable() || !$entity_info->getBaseTable()) {
       continue;
     }
     // This relationship does not use the 'field id' column, if the entity has
@@ -442,9 +442,9 @@ function comment_views_data() {
     // {comment_entity_statistics} for each field as multiple joins between
     // the same two tables is not supported.
     if (\Drupal::service('comment.manager')->getFields($type)) {
-      $data['comment_entity_statistics']['table']['join'][$entity_info['base_table']] = array(
+      $data['comment_entity_statistics']['table']['join'][$entity_info->getBaseTable()] = array(
         'type' => 'INNER',
-        'left_field' => $entity_info['entity_keys']['id'],
+        'left_field' => $entity_info->getKey('id'),
         'field' => 'entity_id',
         'extra' => array(
           array(
@@ -604,11 +604,11 @@ function comment_views_data_alter(&$data) {
 
   // Provide a integration for each entity type except comment.
   foreach (\Drupal::entityManager()->getDefinitions() as $entity_type => $entity_info) {
-    if ($entity_type == 'comment' || empty($entity_info['fieldable']) || !isset($entity_info['base_table'])) {
+    if ($entity_type == 'comment' || !$entity_info->isFieldable() || !$entity_info->getBaseTable()) {
       continue;
     }
     $fields = \Drupal::service('comment.manager')->getFields($entity_type);
-    $base_table = $entity_info['base_table'];
+    $base_table = $entity_info->getBaseTable();
     $args = array('@entity_type' => $entity_type);
 
     if ($fields) {
@@ -620,9 +620,9 @@ function comment_views_data_alter(&$data) {
         ),
       );
 
-      if ($entity_info['id'] == 'node') {
+      if ($entity_type == 'node') {
         // Node properties lives in data_table.
-        $table = $entity_info['data_table'];
+        $table = $entity_info->getDataTable();
       }
       else {
         $table = $base_table;
@@ -637,7 +637,7 @@ function comment_views_data_alter(&$data) {
           'id' => 'argument_comment_user_uid',
           'no group by' => TRUE,
           'entity_type' => $entity_type,
-          'entity_id' => $entity_info['entity_keys']['id'],
+          'entity_id' => $entity_info->getKey('id'),
         ),
         'filter' => array(
           'field' => 'uid',
@@ -645,7 +645,7 @@ function comment_views_data_alter(&$data) {
           'name field' => 'name',
           'id' => 'comment_user_uid',
           'entity_type' => $entity_type,
-          'entity_id' => $entity_info['entity_keys']['id'],
+          'entity_id' => $entity_info->getKey('id'),
         ),
       );
 
@@ -658,7 +658,7 @@ function comment_views_data_alter(&$data) {
             'label' => t('Comments'),
             'base' => 'comment',
             'base field' => 'entity_id',
-            'relationship field' => $entity_info['entity_keys']['id'],
+            'relationship field' => $entity_info->getKey('id'),
             'id' => 'standard',
             'extra' => array(
               array(
diff --git a/core/modules/comment/lib/Drupal/comment/CommentFormController.php b/core/modules/comment/lib/Drupal/comment/CommentFormController.php
index 8a5d52f..3f88b51 100644
--- a/core/modules/comment/lib/Drupal/comment/CommentFormController.php
+++ b/core/modules/comment/lib/Drupal/comment/CommentFormController.php
@@ -354,7 +354,7 @@ public function submit(array $form, array &$form_state) {
    * @param $form_state
    *   A reference to a keyed array containing the current state of the form.
    */
-  public function preview(array $form, array &$form_state) {
+  public function preview(array &$form, array &$form_state) {
     $comment = $this->entity;
     $form_state['comment_preview'] = comment_preview($comment, $form_state);
     $form_state['comment_preview']['#title'] = $this->t('Preview comment');
diff --git a/core/modules/comment/lib/Drupal/comment/CommentManager.php b/core/modules/comment/lib/Drupal/comment/CommentManager.php
index fc833b8..9bca3c5 100644
--- a/core/modules/comment/lib/Drupal/comment/CommentManager.php
+++ b/core/modules/comment/lib/Drupal/comment/CommentManager.php
@@ -58,7 +58,7 @@ public function getParentEntityUri(CommentInterface $comment) {
    */
   public function getFields($entity_type) {
     $info = $this->entityManager->getDefinition($entity_type);
-    if (!is_subclass_of($info['class'], '\Drupal\Core\Entity\ContentEntityInterface')) {
+    if (!$info->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) {
       return array();
     }
 
diff --git a/core/modules/comment/lib/Drupal/comment/CommentStatistics.php b/core/modules/comment/lib/Drupal/comment/CommentStatistics.php
new file mode 100644
index 0000000..2f88f50
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/CommentStatistics.php
@@ -0,0 +1,236 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\comment\CommentStatistics.
+ */
+
+namespace Drupal\comment;
+
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\KeyValueStore\StateInterface;
+use Drupal\Core\Session\AccountInterface;
+
+class CommentStatistics implements CommentStatisticsInterface {
+
+  /**
+   * The current database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $database;
+
+  /**
+   * The current logged in user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
+  /**
+   * The entity manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * The state service.
+   *
+   * @var \Drupal\Core\KeyValueStore\StateInterface
+   */
+  protected $state;
+
+  /**
+   * Constructs the CommentStatistics service.
+   *
+   * @param \Drupal\Core\Database\Connection $database
+   *   The active database connection.
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   The current logged in user.
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager service.
+   * @param \Drupal\Core\KeyValueStore\StateInterface $state
+   *   The state service.
+   */
+  public function __construct(Connection $database, AccountInterface $current_user, EntityManagerInterface $entity_manager, StateInterface $state) {
+    $this->database = $database;
+    $this->currentUser = $current_user;
+    $this->entityManager = $entity_manager;
+    $this->state = $state;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function read($entities, $entity_type) {
+    return $this->database->select('comment_entity_statistics', 'ces')
+      ->fields('ces')
+      ->condition('ces.entity_id', array_keys($entities))
+      ->condition('ces.entity_type', $entity_type)
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete(EntityInterface $entity) {
+    $this->database->delete('comment_entity_statistics')
+      ->condition('entity_id', $entity->id())
+      ->condition('entity_type', $entity->entityType())
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function initializeRecord(ContentEntityInterface $entity, $fields) {
+    $query = $this->database->insert('comment_entity_statistics')
+      ->fields(array(
+        'entity_id',
+        'entity_type',
+        'field_id',
+        'cid',
+        'last_comment_timestamp',
+        'last_comment_name',
+        'last_comment_uid',
+        'comment_count',
+      ));
+    foreach ($fields as $field_name => $detail) {
+      // Skip fields that entity does not have.
+      if (!$entity->hasField($field_name)) {
+        continue;
+      }
+      // There is at least one comment field, the query needs to be executed.
+      // @todo Use $entity->getAuthorId() after https://drupal.org/node/2078387
+      if ($entity->hasField('uid')) {
+        $last_comment_uid = $entity->get('uid')->value;
+      }
+      else {
+        // Default to current user when entity does not have a uid property.
+        $last_comment_uid = $this->currentUser->id();
+      }
+      // Default to REQUEST_TIME when entity does not have a changed property.
+      $last_comment_timestamp = REQUEST_TIME;
+      if ($entity instanceof EntityChangedInterface) {
+        $last_comment_timestamp = $entity->getChangedTime();
+      }
+      $query->values(array(
+        'entity_id' => $entity->id(),
+        'entity_type' => $entity->entityType(),
+        'field_id' => $entity->entityType() . '__' . $field_name,
+        'cid' => 0,
+        'last_comment_timestamp' => $last_comment_timestamp,
+        'last_comment_name' => NULL,
+        'last_comment_uid' => $last_comment_uid,
+        'comment_count' => 0,
+      ));
+    }
+    $query->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function maximumCount($entity_type = 'node') {
+    return $this->database->query('SELECT MAX(comment_count) FROM {comment_entity_statistics} WHERE entity_type = :entity_type', $entity_type)->fetchField();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rankingInfo() {
+    return array(
+      'comments' => array(
+        'title' => t('Number of comments'),
+        'join' => array(
+          'type' => 'LEFT',
+          'table' => 'comment_entity_statistics',
+          'alias' => 'ces',
+          // Default to comment field as this is the most common use case for
+          // nodes.
+          'on' => "ces.entity_id = i.sid AND ces.entity_type = 'node' AND ces.field_id = 'node__comment'",
+        ),
+        // Inverse law that maps the highest reply count on the site to 1 and 0
+        // to 0.
+        'score' => '2.0 - 2.0 / (1.0 + ces.comment_count * CAST(:scale AS DECIMAL))',
+        'arguments' => array(':scale' => $this->state->get('comment.node_comment_statistics_scale') ?: 0),
+      ),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function write(CommentInterface $comment) {
+    // Allow bulk updates and inserts to temporarily disable the maintenance of
+    // the {comment_entity_statistics} table.
+    if (!$this->state->get('comment.maintain_entity_statistics')) {
+      return;
+    }
+    $query = $this->database->select('comment', 'c');
+    $query->addExpression('COUNT(cid)');
+    $count = $query->condition('c.entity_id', $comment->entity_id->value)
+      ->condition('c.entity_type', $comment->entity_type->value)
+      ->condition('c.field_id', $comment->field_id->value)
+      ->condition('c.status', CommentInterface::PUBLISHED)
+      ->execute()
+      ->fetchField();
+
+    if ($count > 0) {
+      // Comments exist.
+      $last_reply = $this->database->select('comment', 'c')
+        ->fields('c', array('cid', 'name', 'changed', 'uid'))
+        ->condition('c.entity_id', $comment->entity_id->value)
+        ->condition('c.entity_type', $comment->entity_type->value)
+        ->condition('c.field_id', $comment->field_id->value)
+        ->condition('c.status', CommentInterface::PUBLISHED)
+        ->orderBy('c.created', 'DESC')
+        ->range(0, 1)
+        ->execute()
+        ->fetchObject();
+      // Use merge here because entity could be created before comment field.
+      $this->database->merge('comment_entity_statistics')
+        ->fields(array(
+          'cid' => $last_reply->cid,
+          'comment_count' => $count,
+          'last_comment_timestamp' => $last_reply->changed,
+          'last_comment_name' => $last_reply->uid ? '' : $last_reply->name,
+          'last_comment_uid' => $last_reply->uid,
+        ))
+        ->key(array(
+          'entity_id' => $comment->entity_id->value,
+          'entity_type' => $comment->entity_type->value,
+          'field_id' => $comment->field_id->value,
+        ))
+        ->execute();
+    }
+    else {
+      // Comments do not exist.
+      $entity = $this->entityManager->getStorageController($comment->entity_type->value)->load($comment->entity_id->value);
+      $this->database->update('comment_entity_statistics')
+        ->fields(array(
+          'cid' => 0,
+          'comment_count' => 0,
+          // Use the created date of the entity if it's set, or default to
+          // REQUEST_TIME.
+          'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTime() : REQUEST_TIME,
+          'last_comment_name' => '',
+          // @todo Use $entity->getAuthorId() after
+          //   https://drupal.org/node/2078387
+          // Get the user ID from the entity if it's set, or default to the
+          // currently logged in user.
+          'last_comment_uid' => $entity->hasField('uid') ? $entity->get('uid')->value : $this->currentUser->id(),
+        ))
+        ->condition('entity_id', $comment->entity_id->value)
+        ->condition('entity_type', $comment->entity_type->value)
+        ->condition('field_id', $comment->field_id->value)
+        ->execute();
+    }
+  }
+
+}
diff --git a/core/modules/comment/lib/Drupal/comment/CommentStatisticsInterface.php b/core/modules/comment/lib/Drupal/comment/CommentStatisticsInterface.php
new file mode 100644
index 0000000..d78968b
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/CommentStatisticsInterface.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\comment\CommentStatisticsInterface.
+ */
+
+namespace Drupal\comment;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Provides an interface for storing and retrieving comment statistics.
+ */
+interface CommentStatisticsInterface {
+
+  /**
+   * Returns an array of ranking information for hook_ranking().
+   *
+   * @return array
+   *   Array of ranking information as expected by hook_ranking().
+   *
+   * @see hook_ranking()
+   * @see comment_ranking()
+   */
+  public function rankingInfo();
+
+  /**
+   * Read comment statistics records for an array of entities.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface[] $entities
+   *   Array of entities on which commenting is enabled, keyed by id
+   * @param string $entity_type
+   *   The entity type of the passed entities.
+   *
+   * @return object[]
+   *   Array of statistics records keyed by entity id.
+   */
+  public function read($entities, $entity_type);
+
+  /**
+   * Delete comment statistics records for an entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity for which comment statistics should be deleted.
+   */
+  public function delete(EntityInterface $entity);
+
+  /**
+   * Update or insert comment statistics records after a comment is added.
+   *
+   * @param \Drupal\comment\CommentInterface $comment
+   *   The comment added or updated.
+   */
+  public function write(CommentInterface $comment);
+
+  /**
+   * Find the maximum number of comments for the given entity type.
+   *
+   * Used to influence search rankings.
+   *
+   * @param string $entity_type
+   *   The entity type to consider when fetching the maximum comment count for.
+   *
+   * @return int
+   *   The maximum number of comments for and entity of the given type.
+   *
+   * @see comment_update_index()
+   */
+  public function maximumCount($entity_type = 'node');
+
+  /**
+   * Insert an empty record for the given entity.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The created entity for which a statistics record is to be initialized.
+   * @param array $fields
+   *   Array of comment field definitions for the given entity.
+   */
+  public function initializeRecord(ContentEntityInterface $entity, $fields);
+
+}
diff --git a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php
index 82cc6e4..6c32f0e 100644
--- a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php
+++ b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php
@@ -7,9 +7,12 @@
 
 namespace Drupal\comment;
 
+use Drupal\Core\Database\Connection;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\FieldableDatabaseStorageController;
-use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\field\FieldInfo;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Defines the controller class for comments.
@@ -20,6 +23,43 @@
 class CommentStorageController extends FieldableDatabaseStorageController implements CommentStorageControllerInterface {
 
   /**
+   * The comment statistics service.
+   *
+   * @var \Drupal\comment\CommentStatisticsInterface
+   */
+  protected $statistics;
+
+  /**
+   * Constructs a CommentStorageController object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Database\Connection $database
+   *   The database connection to be used.
+   * @param \Drupal\field\FieldInfo $field_info
+   *   The field info service.
+   * @param \Drupal\comment\CommentStatisticsInterface $comment_statistics
+   *   The comment statistics service.
+   */
+  public function __construct(EntityTypeInterface $entity_info, Connection $database, FieldInfo $field_info, CommentStatisticsInterface $comment_statistics) {
+    parent::__construct($entity_info, $database, $field_info);
+    $this->statistics = $comment_statistics;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
+    return new static(
+      $entity_info,
+      $container->get('database'),
+      $container->get('field.info'),
+      $container->get('comment.statistics')
+    );
+  }
+
+
+  /**
    * {@inheritdoc}
    */
   protected function buildQuery($ids, $revision_id = FALSE) {
@@ -46,70 +86,7 @@ protected function postLoad(array &$queried_entities) {
    * {@inheritdoc}
    */
   public function updateEntityStatistics(CommentInterface $comment) {
-    // Allow bulk updates and inserts to temporarily disable the maintenance of
-    // the {comment_entity_statistics} table.
-    if (!\Drupal::state()->get('comment.maintain_entity_statistics')) {
-      return;
-    }
-
-    $query = $this->database->select('comment', 'c');
-    $query->addExpression('COUNT(cid)');
-    $count = $query->condition('c.entity_id', $comment->entity_id->value)
-      ->condition('c.entity_type', $comment->entity_type->value)
-      ->condition('c.field_id', $comment->field_id->value)
-      ->condition('c.status', CommentInterface::PUBLISHED)
-      ->execute()
-      ->fetchField();
-
-    if ($count > 0) {
-      // Comments exist.
-      $last_reply = $this->database->select('comment', 'c')
-        ->fields('c', array('cid', 'name', 'changed', 'uid'))
-        ->condition('c.entity_id', $comment->entity_id->value)
-        ->condition('c.entity_type', $comment->entity_type->value)
-        ->condition('c.field_id', $comment->field_id->value)
-        ->condition('c.status', CommentInterface::PUBLISHED)
-        ->orderBy('c.created', 'DESC')
-        ->range(0, 1)
-        ->execute()
-        ->fetchObject();
-      // Use merge here because entity could be created before comment field.
-      $this->database->merge('comment_entity_statistics')
-        ->fields(array(
-          'cid' => $last_reply->cid,
-          'comment_count' => $count,
-          'last_comment_timestamp' => $last_reply->changed,
-          'last_comment_name' => $last_reply->uid ? '' : $last_reply->name,
-          'last_comment_uid' => $last_reply->uid,
-        ))
-        ->key(array(
-          'entity_id' => $comment->entity_id->value,
-          'entity_type' => $comment->entity_type->value,
-          'field_id' => $comment->field_id->value,
-        ))
-        ->execute();
-    }
-    else {
-      // Comments do not exist.
-      $entity = entity_load($comment->entity_type->value, $comment->entity_id->value);
-      $this->database->update('comment_entity_statistics')
-        ->fields(array(
-          'cid' => 0,
-          'comment_count' => 0,
-          // Use the created date of the entity if it's set, or default to
-          // REQUEST_TIME.
-          'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTime() : REQUEST_TIME,
-          'last_comment_name' => '',
-          // @todo Use $entity->getAuthorId() after https://drupal.org/node/2078387
-          // Get the user ID from the entity if it's set, or default to the
-          // currently logged in user.
-          'last_comment_uid' => $entity->hasField('uid') ? $entity->get('uid')->value : \Drupal::currentUser()->id(),
-        ))
-        ->condition('entity_id', $comment->entity_id->value)
-        ->condition('entity_type', $comment->entity_type->value)
-        ->condition('field_id', $comment->field_id->value)
-        ->execute();
-    }
+    $this->statistics->write($comment);
   }
 
   /**
diff --git a/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php b/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php
index dc4cd61..7dd6517 100644
--- a/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php
+++ b/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityViewBuilderInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Entity\EntityViewBuilder;
@@ -47,9 +48,8 @@ class CommentViewBuilder extends EntityViewBuilder implements EntityViewBuilderI
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
       $container->get('entity.manager'),
       $container->get('field.info'),
@@ -61,9 +61,7 @@ public static function createInstance(ContainerInterface $container, $entity_typ
   /**
    * Constructs a new CommentViewBuilder.
    *
-   * @param string $entity_type
-   *   The entity type.
-   * @param array $entity_info
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
    *   The entity information array.
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager service.
@@ -74,8 +72,8 @@ public static function createInstance(ContainerInterface $container, $entity_typ
    * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
    *   The CSRF token manager service.
    */
-  public function __construct($entity_type, array $entity_info, EntityManagerInterface $entity_manager, FieldInfo $field_info, ModuleHandlerInterface $module_handler, CsrfTokenGenerator $csrf_token) {
-    parent::__construct($entity_type, $entity_info, $entity_manager);
+  public function __construct(EntityTypeInterface $entity_info, EntityManagerInterface $entity_manager, FieldInfo $field_info, ModuleHandlerInterface $module_handler, CsrfTokenGenerator $csrf_token) {
+    parent::__construct($entity_info, $entity_manager);
     $this->fieldInfo = $field_info;
     $this->moduleHandler = $module_handler;
     $this->csrfToken = $csrf_token;
diff --git a/core/modules/comment/lib/Drupal/comment/Controller/AdminController.php b/core/modules/comment/lib/Drupal/comment/Controller/AdminController.php
index 451a62d..22963bd 100644
--- a/core/modules/comment/lib/Drupal/comment/Controller/AdminController.php
+++ b/core/modules/comment/lib/Drupal/comment/Controller/AdminController.php
@@ -121,7 +121,7 @@ public function overviewBundles() {
           }
         }
 
-        $row['data']['type']['data'] = String::checkPlain($entity_types[$entity_type]['label']);
+        $row['data']['type']['data'] = String::checkPlain($entity_types[$entity_type]->getLabel());
 
         if ($field_ui_enabled) {
           if ($this->currentUser()->hasPermission('administer comment fields')) {
@@ -189,7 +189,7 @@ public function bundleInfo($commented_entity_type, $field_name) {
 
     $build['usage'] = array(
       '#theme' => 'item_list',
-      '#title' => String::checkPlain($entity_type_info['label']),
+      '#title' => String::checkPlain($entity_type_info->getLabel()),
       '#items' => array(),
     );
     // Loop over all of bundles to which this comment field is attached.
diff --git a/core/modules/comment/tests/modules/comment_test/comment_test.module b/core/modules/comment/tests/modules/comment_test/comment_test.module
index da43a04..110e8c5 100644
--- a/core/modules/comment/tests/modules/comment_test/comment_test.module
+++ b/core/modules/comment/tests/modules/comment_test/comment_test.module
@@ -11,10 +11,13 @@
 /**
  * Implements hook_entity_info_alter().
  */
-function comment_test_entity_info_alter(&$info) {
+function comment_test_entity_info_alter(&$entity_info) {
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
   if (language_multilingual()) {
     // Enable language handling for comment fields.
-    $info['comment']['translation']['comment_test'] = TRUE;
+    $translation = $entity_info['comment']->get('translation');
+    $translation['comment_test'] = TRUE;
+    $entity_info['comment']->set('translation', $translation);
   }
 }
 
diff --git a/core/modules/config/config.local_tasks.yml b/core/modules/config/config.local_tasks.yml
index b24f53b..5a894f0 100644
--- a/core/modules/config/config.local_tasks.yml
+++ b/core/modules/config/config.local_tasks.yml
@@ -1,38 +1,34 @@
 config.sync:
   route_name: config.sync
-  tab_root_id: config.sync
+  base_route: config.sync
   title: 'Synchronize'
 
 config.full:
   route_name: config.import_full
   title: 'Full Import/Export'
-  tab_root_id: config.sync
+  base_route: config.sync
 
 config.single:
   route_name: config.import_single
   title: 'Single Import/Export'
-  tab_root_id: config.sync
+  base_route: config.sync
 
 config.export_full:
   route_name: config.export_full
   title: Export
-  tab_root_id: config.sync
-  tab_parent_id: config.full
+  parent_id: config.full
 
 config.import_full:
   route_name: config.import_full
   title: Import
-  tab_root_id: config.sync
-  tab_parent_id: config.full
+  parent_id: config.full
 
 config.export_single:
   route_name: config.export_single
   title: Export
-  tab_root_id: config.sync
-  tab_parent_id: config.single
+  parent_id: config.single
 
 config.import_single:
   route_name: config.import_single
   title: Import
-  tab_root_id: config.sync
-  tab_parent_id: config.single
+  parent_id: config.single
diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSingleExportForm.php b/core/modules/config/lib/Drupal/config/Form/ConfigSingleExportForm.php
index 5ac759b..2f146c6 100644
--- a/core/modules/config/lib/Drupal/config/Form/ConfigSingleExportForm.php
+++ b/core/modules/config/lib/Drupal/config/Form/ConfigSingleExportForm.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\MapArray;
 use Drupal\Core\Config\StorageInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Form\FormBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -35,7 +36,7 @@ class ConfigSingleExportForm extends FormBase {
   /**
    * Tracks the valid config entity type definitions.
    *
-   * @var array
+   * @var \Drupal\Core\Entity\EntityTypeInterface[]
    */
   protected $definitions = array();
 
@@ -74,12 +75,12 @@ public function getFormID() {
    */
   public function buildForm(array $form, array &$form_state, $config_type = NULL, $config_name = NULL) {
     foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
-      if (isset($definition['config_prefix']) && isset($definition['entity_keys']['uuid'])) {
+      if ($definition->getConfigPrefix() && $definition->hasKey('uuid')) {
         $this->definitions[$entity_type] = $definition;
       }
     }
-    $entity_types = array_map(function ($definition) {
-      return $definition['label'];
+    $entity_types = array_map(function (EntityTypeInterface $definition) {
+      return $definition->getLabel();
     }, $this->definitions);
     // Sort the entity types by label, then add the simple config to the top.
     uasort($entity_types, 'strnatcasecmp');
@@ -144,7 +145,7 @@ public function updateExport($form, &$form_state) {
     // Determine the full config name for the selected config entity.
     if ($form_state['values']['config_type'] !== 'system.simple') {
       $definition = $this->entityManager->getDefinition($form_state['values']['config_type']);
-      $name = $definition['config_prefix'] . '.' . $form_state['values']['config_name'];
+      $name = $definition->getConfigPrefix() . '.' . $form_state['values']['config_name'];
     }
     // The config name is used directly for simple configuration.
     else {
@@ -176,8 +177,8 @@ protected function findConfiguration($config_type) {
     // Handle simple configuration.
     else {
       // Gather the config entity prefixes.
-      $config_prefixes = array_map(function ($definition) {
-        return $definition['config_prefix'] . '.';
+      $config_prefixes = array_map(function (EntityTypeInterface $definition) {
+        return $definition->getConfigPrefix() . '.';
       }, $this->definitions);
 
       // Find all config, and then filter our anything matching a config prefix.
diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php b/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php
index 89f71be..b89f68b 100644
--- a/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php
+++ b/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php
@@ -90,12 +90,12 @@ public function getCancelRoute() {
   public function getQuestion() {
     if ($this->data['config_type'] === 'system.simple') {
       $name = $this->data['config_name'];
-      $type = $this->t('Simple configuration');
+      $type = $this->t('simple configuration');
     }
     else {
       $definition = $this->entityManager->getDefinition($this->data['config_type']);
-      $name = $this->data['import'][$definition['entity_keys']['id']];
-      $type = $definition['label'];
+      $name = $this->data['import'][$definition->getKey('id')];
+      $type = $definition->getLowercaseLabel();
     }
 
     $args = array(
@@ -122,8 +122,8 @@ public function buildForm(array $form, array &$form_state) {
 
     $entity_types = array();
     foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
-      if (isset($definition['config_prefix']) && isset($definition['entity_keys']['uuid'])) {
-        $entity_types[$entity_type] = $definition['label'];
+      if ($definition->getConfigPrefix() && $definition->hasKey('uuid')) {
+        $entity_types[$entity_type] = $definition->getLabel();
       }
     }
     // Sort the entity types by label, then add the simple config to the top.
@@ -180,14 +180,14 @@ public function validateForm(array &$form, array &$form_state) {
     // Validate for config entities.
     if ($form_state['values']['config_type'] !== 'system.simple') {
       $definition = $this->entityManager->getDefinition($form_state['values']['config_type']);
-      $id_key = $definition['entity_keys']['id'];
+      $id_key = $definition->getKey('id');
       $entity_storage = $this->entityManager->getStorageController($form_state['values']['config_type']);
       // If an entity ID was not specified, set an error.
       if (!isset($data[$id_key])) {
-        $this->setFormError('import', $form_state, $this->t('Missing ID key "@id_key" for this @entity_type import.', array('@id_key' => $id_key, '@entity_type' => $definition['label'])));
+        $this->setFormError('import', $form_state, $this->t('Missing ID key "@id_key" for this @entity_type import.', array('@id_key' => $id_key, '@entity_type' => $definition->getLabel())));
         return;
       }
-      $uuid_key = $definition['entity_keys']['uuid'];
+      $uuid_key = $definition->getKey('uuid');
       // If there is an existing entity, ensure matching ID and UUID.
       if ($entity = $entity_storage->load($data[$id_key])) {
         $this->configExists = $entity;
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityUnitTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityUnitTest.php
index a7ba6dc..262fd46 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityUnitTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityUnitTest.php
@@ -49,16 +49,16 @@ protected function setUp() {
    * Tests storage controller methods.
    */
   public function testStorageControllerMethods() {
-    $info = entity_get_info('config_test');
+    $info = \Drupal::entityManager()->getDefinition('config_test');
 
-    $expected = $info['config_prefix'] . '.';
+    $expected = $info->getConfigPrefix() . '.';
     $this->assertIdentical($this->storage->getConfigPrefix(), $expected);
 
     // Test the static extractID() method.
     $expected_id = 'test_id';
-    $config_name = $info['config_prefix'] . '.' . $expected_id;
+    $config_name = $info->getConfigPrefix() . '.' . $expected_id;
     $storage = $this->storage;
-    $this->assertIdentical($storage::getIDFromConfigName($config_name, $info['config_prefix']), $expected_id);
+    $this->assertIdentical($storage::getIDFromConfigName($config_name, $info->getConfigPrefix()), $expected_id);
 
     // Create three entities, two with the same style.
     $style = $this->randomName(8);
diff --git a/core/modules/config/tests/config_test/config_test.local_tasks.yml b/core/modules/config/tests/config_test/config_test.local_tasks.yml
index 989f131..d05ae41 100644
--- a/core/modules/config/tests/config_test/config_test.local_tasks.yml
+++ b/core/modules/config/tests/config_test/config_test.local_tasks.yml
@@ -1,4 +1,4 @@
 config_test.entity_tab:
   route_name: config_test.entity
   title: 'Edit'
-  tab_root_id: config_test.entity_tab
+  base_route: config_test.entity
diff --git a/core/modules/config/tests/config_test/config_test.module b/core/modules/config/tests/config_test/config_test.module
index 9c0b042..0c3b782 100644
--- a/core/modules/config/tests/config_test/config_test.module
+++ b/core/modules/config/tests/config_test/config_test.module
@@ -67,13 +67,19 @@ function config_test_config_test_create(ConfigTest $config_test) {
  * Implements hook_entity_info_alter().
  */
 function config_test_entity_info_alter(&$entity_info) {
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
   // The 'translatable' entity key is not supposed to change over time. In this
   // case we can safely do it because we set it once and we do not change it for
   // all the duration of the test session.
-  $entity_info['config_test']['translatable'] = \Drupal::service('state')->get('config_test.translatable');
+  $entity_info['config_test']->set('translatable', \Drupal::service('state')->get('config_test.translatable'));
 
   // Create a clone of config_test that does not have a status.
-  $entity_info['config_test_no_status'] = $entity_info['config_test'];
-  unset($entity_info['config_test_no_status']['entity_keys']['status']);
-  $entity_info['config_test_no_status']['config_prefix'] = 'config_test.no_status';
+  $entity_info['config_test_no_status'] = clone $entity_info['config_test'];
+  $config_test_no_status = &$entity_info['config_test_no_status'];
+
+  $keys = $config_test_no_status->getKeys();
+  unset($keys['status']);
+  $config_test_no_status->set('id', 'config_test_no_status');
+  $config_test_no_status->set('entity_keys', $keys);
+  $config_test_no_status->set('config_prefix', 'config_test.no_status');
 }
diff --git a/core/modules/config/tests/config_test/config_test.routing.yml b/core/modules/config/tests/config_test/config_test.routing.yml
index 7b973ab..7af61cd 100644
--- a/core/modules/config/tests/config_test/config_test.routing.yml
+++ b/core/modules/config/tests/config_test/config_test.routing.yml
@@ -15,7 +15,7 @@ config_test.entity_add:
 config_test.entity:
   path: '/admin/structure/config_test/manage/{config_test}'
   defaults:
-    _controller: '\Drupal\config_test\ConfigTestController::edit'
+    _content: '\Drupal\config_test\ConfigTestController::edit'
     entity_type: 'config_test'
   requirements:
     _access: 'TRUE'
diff --git a/core/modules/config_translation/config_translation.api.php b/core/modules/config_translation/config_translation.api.php
index 6f98634..aa1f7f0 100644
--- a/core/modules/config_translation/config_translation.api.php
+++ b/core/modules/config_translation/config_translation.api.php
@@ -49,7 +49,7 @@ function hook_config_translation_info(&$info) {
       }
 
       // Make sure entity type is fieldable and has a base route.
-      if ($entity_info['fieldable'] && !empty($base_route)) {
+      if ($entity_info->isFieldable() && !empty($base_route)) {
         $info[$entity_type . '_fields'] = array(
           'base_route_name' => 'field_ui.instance_edit_' . $entity_type,
           'entity_type' => 'field_instance',
diff --git a/core/modules/config_translation/config_translation.module b/core/modules/config_translation/config_translation.module
index 0fc1170..33a2ee7 100644
--- a/core/modules/config_translation/config_translation.module
+++ b/core/modules/config_translation/config_translation.module
@@ -90,7 +90,7 @@ function config_translation_config_translation_info(&$info) {
       }
 
       // Make sure entity type is fieldable and has a base route.
-      if ($entity_info['fieldable'] && !empty($base_route)) {
+      if ($entity_info->isFieldable() && !empty($base_route)) {
         $info[$entity_type . '_fields'] = array(
           'base_route_name' => 'field_ui.instance_edit_' . $entity_type,
           'entity_type' => 'field_instance',
@@ -109,8 +109,8 @@ function config_translation_config_translation_info(&$info) {
     // Determine base path for entities automatically if provided via the
     // configuration entity.
     if (
-      !in_array('Drupal\Core\Config\Entity\ConfigEntityInterface', class_implements($entity_info['class'])) ||
-      !isset($entity_info['links']['edit-form'])
+      !$entity_info->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface') ||
+      !$entity_info->hasLinkTemplate('edit-form')
     ) {
       // Do not record this entity mapper if the entity type does not
       // provide a base route. We'll surely not be able to do anything with
@@ -122,7 +122,7 @@ function config_translation_config_translation_info(&$info) {
     // Use the entity type as the plugin ID.
     $info[$entity_type] = array(
       'class' => '\Drupal\config_translation\ConfigEntityMapper',
-      'base_route_name' => $entity_info['links']['edit-form'],
+      'base_route_name' => $entity_info->getLinkTemplate('edit-form'),
       'title' => '!label !entity_type',
       'names' => array(),
       'entity_type' => $entity_type,
@@ -175,12 +175,3 @@ function config_translation_library_info() {
   );
   return $libraries;
 }
-
-/**
- * Implements hook_local_tasks_alter().
- */
-function config_translation_local_tasks_alter(&$local_tasks) {
-  // Alters in tab_root_ids onto the config translation local tasks.
-  $derivative = ConfigTranslationLocalTasks::create(\Drupal::getContainer(), 'config_translation.local_tasks');
-  $derivative->alterLocalTasks($local_tasks);
-}
diff --git a/core/modules/config_translation/config_translation.services.yml b/core/modules/config_translation/config_translation.services.yml
index f30e569..19d5c62 100644
--- a/core/modules/config_translation/config_translation.services.yml
+++ b/core/modules/config_translation/config_translation.services.yml
@@ -9,13 +9,13 @@ services:
     class: Drupal\config_translation\Access\ConfigTranslationOverviewAccess
     arguments: ['@plugin.manager.config_translation.mapper']
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _config_translation_overview_access }
 
   config_translation.access.form:
     class: Drupal\config_translation\Access\ConfigTranslationFormAccess
     arguments: ['@plugin.manager.config_translation.mapper']
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _config_translation_form_access }
 
   plugin.manager.config_translation.mapper:
     class: Drupal\config_translation\ConfigMapperManager
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Access/ConfigTranslationFormAccess.php b/core/modules/config_translation/lib/Drupal/config_translation/Access/ConfigTranslationFormAccess.php
index 5e98d41..10c5f25 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Access/ConfigTranslationFormAccess.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Access/ConfigTranslationFormAccess.php
@@ -19,13 +19,6 @@ class ConfigTranslationFormAccess extends ConfigTranslationOverviewAccess {
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    return array('_config_translation_form_access');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     // For the translation forms we have a target language, so we need some
     // checks in addition to the checks performed for the translation overview.
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Access/ConfigTranslationOverviewAccess.php b/core/modules/config_translation/lib/Drupal/config_translation/Access/ConfigTranslationOverviewAccess.php
index 73c6ab0..1834416 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Access/ConfigTranslationOverviewAccess.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Access/ConfigTranslationOverviewAccess.php
@@ -8,7 +8,7 @@
 namespace Drupal\config_translation\Access;
 
 use Drupal\config_translation\ConfigMapperManagerInterface;
-use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
@@ -16,7 +16,7 @@
 /**
  * Checks access for displaying the configuration translation overview.
  */
-class ConfigTranslationOverviewAccess implements StaticAccessCheckInterface {
+class ConfigTranslationOverviewAccess implements AccessInterface {
 
   /**
    * The mapper plugin discovery service.
@@ -45,13 +45,6 @@ public function __construct(ConfigMapperManagerInterface $config_mapper_manager)
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    return array('_config_translation_overview_access');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */
     $mapper = $this->configMapperManager->createInstance($route->getDefault('plugin_id'));
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/ConfigEntityMapper.php b/core/modules/config_translation/lib/Drupal/config_translation/ConfigEntityMapper.php
index 44e0d7f..d8d3297 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/ConfigEntityMapper.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/ConfigEntityMapper.php
@@ -7,10 +7,9 @@
 
 namespace Drupal\config_translation;
 
-use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Config\ConfigFactory;
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Drupal\locale\LocaleConfigManager;
@@ -25,7 +24,7 @@ class ConfigEntityMapper extends ConfigNamesMapper {
   /**
    * The entity manager.
    *
-   * @var \Drupal\Core\Entity\EntityManager
+   * @var \Drupal\Core\Entity\EntityManagerInterface
    */
   protected $entityManager;
 
@@ -69,10 +68,10 @@ class ConfigEntityMapper extends ConfigNamesMapper {
    *   The route provider.
    * @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
    *   The string translation manager.
-   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
    */
-  public function __construct($plugin_id, array $plugin_definition, ConfigFactory $config_factory, LocaleConfigManager $locale_config_manager, ConfigMapperManagerInterface $config_mapper_manager, RouteProviderInterface $route_provider, TranslationInterface $translation_manager, EntityManager $entity_manager) {
+  public function __construct($plugin_id, array $plugin_definition, ConfigFactory $config_factory, LocaleConfigManager $locale_config_manager, ConfigMapperManagerInterface $config_mapper_manager, RouteProviderInterface $route_provider, TranslationInterface $translation_manager, EntityManagerInterface $entity_manager) {
     parent::__construct($plugin_id, $plugin_definition, $config_factory, $locale_config_manager, $config_mapper_manager, $route_provider, $translation_manager);
     $this->setType($plugin_definition['entity_type']);
 
@@ -134,7 +133,7 @@ public function setEntity(EntityInterface $entity) {
     // entity. This is not a Drupal 8 best practice (ideally the configuration
     // would have pluggable components), but this may happen as well.
     $entity_type_info = $this->entityManager->getDefinition($this->entityType);
-    $this->addConfigName($entity_type_info['config_prefix'] . '.' . $entity->id());
+    $this->addConfigName($entity_type_info->getConfigPrefix() . '.' . $entity->id());
 
     return TRUE;
   }
@@ -147,7 +146,7 @@ public function getTitle() {
     // current page language. The title placeholder is later escaped for
     // display.
     $entity_type_info = $this->entityManager->getDefinition($this->entityType);
-    return $this->t($this->pluginDefinition['title'], array('!label' => $this->entity->label(), '!entity_type' => Unicode::strtolower($entity_type_info['label'])));
+    return $this->t($this->pluginDefinition['title'], array('!label' => $this->entity->label(), '!entity_type' => $entity_type_info->getLowercaseLabel()));
   }
 
   /**
@@ -194,7 +193,7 @@ public function getType() {
    */
   public function getTypeName() {
     $entity_type_info = $this->entityManager->getDefinition($this->entityType);
-    return $entity_type_info['label'];
+    return $entity_type_info->getLabel();
   }
 
   /**
@@ -202,7 +201,7 @@ public function getTypeName() {
    */
   public function getTypeLabel() {
     $entityType = $this->entityManager->getDefinition($this->entityType);
-    return $entityType['label'];
+    return $entityType->getLabel();
   }
 
   /**
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/ConfigFieldInstanceMapper.php b/core/modules/config_translation/lib/Drupal/config_translation/ConfigFieldInstanceMapper.php
index 44df444..8323461 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/ConfigFieldInstanceMapper.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/ConfigFieldInstanceMapper.php
@@ -31,7 +31,7 @@ class ConfigFieldInstanceMapper extends ConfigEntityMapper {
   public function getBaseRouteParameters() {
     $parameters = parent::getBaseRouteParameters();
     $base_entity_info = $this->entityManager->getDefinition($this->pluginDefinition['base_entity_type']);
-    $parameters[$base_entity_info['bundle_entity_type']] = $this->entity->targetBundle();
+    $parameters[$base_entity_info->getBundleEntityType()] = $this->entity->targetBundle();
     return $parameters;
   }
 
@@ -40,7 +40,7 @@ public function getBaseRouteParameters() {
    */
   public function getTypeLabel() {
     $base_entity_info = $this->entityManager->getDefinition($this->pluginDefinition['base_entity_type']);
-    return $this->t('@label fields', array('@label' => $base_entity_info['label']));
+    return $this->t('@label fields', array('@label' => $base_entity_info->getLabel()));
   }
 
 }
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationBlockListController.php b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationBlockListController.php
index bd0f6f5..35c10cc 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationBlockListController.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationBlockListController.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\String;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 
 /**
@@ -27,8 +28,8 @@ class ConfigTranslationBlockListController extends ConfigTranslationEntityListCo
   /**
    * {@inheritdoc}
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler) {
+    parent::__construct($entity_info, $storage, $module_handler);
     $this->themes = list_themes();
   }
 
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationFieldInstanceListController.php b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationFieldInstanceListController.php
index d7ad320..2f8630a 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationFieldInstanceListController.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationFieldInstanceListController.php
@@ -10,8 +10,9 @@
 use Drupal\Component\Utility\String;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\field\Field;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -45,7 +46,7 @@ class ConfigTranslationFieldInstanceListController extends ConfigTranslationEnti
   /**
    * The entity manager.
    *
-   * @var \Drupal\Core\Entity\EntityManager
+   * @var \Drupal\Core\Entity\EntityManagerInterface
    */
   protected $entityManager;
 
@@ -54,21 +55,18 @@ class ConfigTranslationFieldInstanceListController extends ConfigTranslationEnti
    *
    * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
    *   The service container this object should use.
-   * @param string $entity_type
-   *   The entity type which the controller handles.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param array $definition
    *   (optional) The plugin definition of the config translation mapper.
    *
    * @return static
    *   A new instance of the entity controller.
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info, array $definition = array()) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info, array $definition = array()) {
     return new static(
-      $entity_type,
       $entity_info,
-      $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getStorageController($entity_info->id()),
       $container->get('module_handler'),
       $container->get('entity.manager'),
       $definition
@@ -78,21 +76,19 @@ public static function createInstance(ContainerInterface $container, $entity_typ
   /**
    * Constructs a new ConfigTranslationFieldInstanceListController object.
    *
-   * @param string $entity_type
-   *   The type of entity to be listed.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
-   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
    * @param array $definition
    *   The plugin definition of the config translation mapper.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, EntityManager $entity_manager, array $definition) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, EntityManagerInterface $entity_manager, array $definition) {
+    parent::__construct($entity_info, $storage, $module_handler);
     $this->entityManager = $entity_manager;
     $this->baseEntityType = $definition['base_entity_type'];
     $this->baseEntityInfo = $this->entityManager->getDefinition($this->baseEntityType);
@@ -117,7 +113,7 @@ public function load() {
    */
   public function getFilterLabels() {
     $info = parent::getFilterLabels();
-    $bundle = isset($this->baseEntityInfo['bundle_label']) ? $this->baseEntityInfo['bundle_label'] : $this->t('Bundle');
+    $bundle = $this->baseEntityInfo->getBundleLabel() ?: $this->t('Bundle');
     $bundle = Unicode::strtolower($bundle);
 
     $info['placeholder'] = $this->t('Enter field or @bundle', array('@bundle' => $bundle));
@@ -152,7 +148,7 @@ public function buildRow(EntityInterface $entity) {
   public function buildHeader() {
     $header['label'] = $this->t('Field');
     if ($this->displayBundle()) {
-      $header['bundle'] = isset($this->baseEntityInfo['bundle_label']) ? $this->baseEntityInfo['bundle_label'] : $this->t('Bundle');
+      $header['bundle'] = $this->baseEntityInfo->getBundleLabel() ?: $this->t('Bundle');
     }
     return $header + parent::buildHeader();
   }
@@ -165,7 +161,7 @@ public function buildHeader() {
    */
   public function displayBundle() {
     // The bundle key is explicitly defined in the entity definition.
-    if (isset($this->baseEntityInfo['bundle_keys']['bundle'])) {
+    if ($this->baseEntityInfo->getBundleKey('bundle')) {
       return TRUE;
     }
 
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationListController.php b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationListController.php
index 48ecad7..14bd763 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationListController.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationListController.php
@@ -76,7 +76,7 @@ public function listing() {
     // list controller.
     $class = $this->mapperDefinition['list_controller'];
     /** @var \Drupal\config_translation\Controller\ConfigTranslationEntityListControllerInterface $controller */
-    $controller = new $class($entity_type, $this->entityManager()->getDefinition($entity_type), $this->entityManager()->getStorageController($entity_type), $this->moduleHandler(), $this->entityManager(), $this->mapperDefinition);
+    $controller = new $class($this->entityManager()->getDefinition($entity_type), $this->entityManager()->getStorageController($entity_type), $this->moduleHandler(), $this->entityManager(), $this->mapperDefinition);
     $build = $controller->render();
     $build['#title'] = $this->mapper->getTypeLabel();
     return $build;
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Plugin/Derivative/ConfigTranslationLocalTasks.php b/core/modules/config_translation/lib/Drupal/config_translation/Plugin/Derivative/ConfigTranslationLocalTasks.php
index c7ccba3..20b9c79 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Plugin/Derivative/ConfigTranslationLocalTasks.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Plugin/Derivative/ConfigTranslationLocalTasks.php
@@ -8,14 +8,14 @@
 namespace Drupal\config_translation\Plugin\Derivative;
 
 use Drupal\config_translation\ConfigMapperManagerInterface;
-use Drupal\Core\Menu\LocalTaskDerivativeBase;
+use Drupal\Component\Plugin\Derivative\DerivativeBase;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Provides dynamic local tasks for config translation.
  */
-class ConfigTranslationLocalTasks extends LocalTaskDerivativeBase implements ContainerDerivativeInterface {
+class ConfigTranslationLocalTasks extends DerivativeBase implements ContainerDerivativeInterface {
 
   /**
    * The mapper plugin discovery service.
@@ -62,32 +62,16 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
     foreach ($mappers as $plugin_id => $mapper) {
       /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */
       $route_name = $mapper->getOverviewRouteName();
-      $this->derivatives[$route_name] = $base_plugin_definition;
-      $this->derivatives[$route_name]['config_translation_plugin_id'] = $plugin_id;
-      $this->derivatives[$route_name]['class'] = '\Drupal\config_translation\Plugin\Menu\LocalTask\ConfigTranslationLocalTask';
-      $this->derivatives[$route_name]['route_name'] = $route_name;
-    }
-    return parent::getDerivativeDefinitions($base_plugin_definition);
-  }
-
-  /**
-   * Alters the local tasks to find the proper tab_root_id for each task.
-   */
-  public function alterLocalTasks(array &$local_tasks) {
-    $mappers = $this->mapperManager->getMappers();
-    foreach ($mappers as $mapper) {
-      /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */
-      $route_name = $mapper->getOverviewRouteName();
-      $translation_tab = $this->basePluginId . ':' . $route_name;
-      $tab_root_id = $this->getPluginIdFromRoute($mapper->getBaseRouteName(), $local_tasks);
-      if (!empty($tab_root_id)) {
-        $local_tasks[$translation_tab]['tab_root_id'] = $tab_root_id;
-      }
-      else {
-        unset($local_tasks[$translation_tab]);
+      $base_route = $mapper->getBaseRouteName();
+      if (!empty($base_route)) {
+        $this->derivatives[$route_name] = $base_plugin_definition;
+        $this->derivatives[$route_name]['config_translation_plugin_id'] = $plugin_id;
+        $this->derivatives[$route_name]['class'] = '\Drupal\config_translation\Plugin\Menu\LocalTask\ConfigTranslationLocalTask';
+        $this->derivatives[$route_name]['route_name'] = $route_name;
+        $this->derivatives[$route_name]['base_route'] = $base_route;
       }
     }
+    return parent::getDerivativeDefinitions($base_plugin_definition);
   }
 
-
 }
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationOverviewTest.php b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationOverviewTest.php
index 9ed4a20..a471ce9 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationOverviewTest.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationOverviewTest.php
@@ -8,7 +8,6 @@
 namespace Drupal\config_translation\Tests;
 
 use Drupal\Component\Utility\String;
-use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Language\Language;
 use Drupal\simpletest\WebTestBase;
 
@@ -97,13 +96,13 @@ public function testMapperListPage() {
       $entity_info = \Drupal::entityManager()->getDefinition($test_entity->entityType());
       $this->drupalGet($base_url . '/translate');
 
-      $title = t('!label !entity_type', array('!label' => $test_entity->label(), '!entity_type' => Unicode::strtolower($entity_info['label'])));
+      $title = t('!label !entity_type', array('!label' => $test_entity->label(), '!entity_type' => $entity_info->getLowercaseLabel()));
       $title = t('Translations for %label', array('%label' => $title));
       $this->assertRaw($title);
       $this->assertRaw('<th>' . t('Language') . '</th>');
 
       $this->drupalGet($base_url);
-      $this->assertLink(t('Translate @title', array('@title' => Unicode::strtolower($entity_info['label']))));
+      $this->assertLink(t('Translate @title', array('@title' => $entity_info->getLowercaseLabel())));
     }
   }
 
diff --git a/core/modules/config_translation/tests/Drupal/config_translation/Tests/ConfigEntityMapperTest.php b/core/modules/config_translation/tests/Drupal/config_translation/Tests/ConfigEntityMapperTest.php
index df9aa0c..b7ed1f5 100644
--- a/core/modules/config_translation/tests/Drupal/config_translation/Tests/ConfigEntityMapperTest.php
+++ b/core/modules/config_translation/tests/Drupal/config_translation/Tests/ConfigEntityMapperTest.php
@@ -59,9 +59,7 @@ public static function getInfo() {
   }
 
   public function setUp() {
-    $this->entityManager = $this->getMockBuilder('Drupal\Core\Entity\EntityManager')
-      ->disableOriginalConstructor()
-      ->getMock();
+    $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
 
     $this->entity = $this->getMock('Drupal\Core\Entity\EntityInterface');
 
@@ -107,11 +105,12 @@ public function testSetEntity() {
       ->with()
       ->will($this->returnValue('entity_id'));
 
+    $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
     $this->entityManager
       ->expects($this->once())
       ->method('getDefinition')
       ->with('language_entity')
-      ->will($this->returnValue(array('config_prefix' => 'config_prefix')));
+      ->will($this->returnValue($entity_type));
 
     $result = $this->configEntityMapper->setEntity($this->entity);
     $this->assertTrue($result);
@@ -125,6 +124,12 @@ public function testSetEntity() {
    * Tests ConfigEntityMapper::getOverviewRouteParameters().
    */
   public function testGetOverviewRouteParameters() {
+    $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+    $this->entityManager
+      ->expects($this->once())
+      ->method('getDefinition')
+      ->with('language_entity')
+      ->will($this->returnValue($entity_type));
     $this->configEntityMapper->setEntity($this->entity);
 
     $this->entity
@@ -150,11 +155,15 @@ public function testGetType() {
    * Tests ConfigEntityMapper::getTypeName().
    */
   public function testGetTypeName() {
+    $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+    $entity_type->expects($this->once())
+      ->method('getLabel')
+      ->will($this->returnValue('test'));
     $this->entityManager
       ->expects($this->once())
       ->method('getDefinition')
       ->with('language_entity')
-      ->will($this->returnValue(array('label' => 'test')));
+      ->will($this->returnValue($entity_type));
 
     $result = $this->configEntityMapper->getTypeName();
     $this->assertSame('test', $result);
@@ -164,11 +173,15 @@ public function testGetTypeName() {
    * Tests ConfigEntityMapper::getTypeLabel().
    */
   public function testGetTypeLabel() {
+    $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+    $entity_type->expects($this->once())
+      ->method('getLabel')
+      ->will($this->returnValue('test'));
     $this->entityManager
       ->expects($this->once())
       ->method('getDefinition')
       ->with('language_entity')
-      ->will($this->returnValue(array('label' => 'test')));
+      ->will($this->returnValue($entity_type));
 
     $result = $this->configEntityMapper->getTypeLabel();
     $this->assertSame('test', $result);
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 4d651f4..ac6fb3f 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
@@ -8,10 +8,10 @@
 /**
  * Implements hook_entity_info_alter().
  */
-function config_translation_test_entity_info_alter(&$info) {
+function config_translation_test_entity_info_alter(&$entity_info) {
   // Remove entity definition for these entity types from config_test module.
-  unset($info['config_test_no_status']);
-  unset($info['config_query_test']);
+  unset($entity_info['config_test_no_status']);
+  unset($entity_info['config_query_test']);
 }
 
 /**
diff --git a/core/modules/contact/contact.local_tasks.yml b/core/modules/contact/contact.local_tasks.yml
index 2eee1ee..e613a83 100644
--- a/core/modules/contact/contact.local_tasks.yml
+++ b/core/modules/contact/contact.local_tasks.yml
@@ -1,10 +1,10 @@
 contact.category_edit:
   title: 'Edit'
   route_name: contact.category_edit
-  tab_root_id: contact.category_edit
+  base_route: contact.category_edit
 
 contact.personal_page:
   title: 'Contact'
   route_name: contact.personal_page
   weight: 2
-  tab_root_id: user.view
+  base_route: user.view
diff --git a/core/modules/contact/contact.services.yml b/core/modules/contact/contact.services.yml
index cccd1fd..acb2866 100644
--- a/core/modules/contact/contact.services.yml
+++ b/core/modules/contact/contact.services.yml
@@ -2,5 +2,5 @@ services:
   access_check.contact_personal:
     class: Drupal\contact\Access\ContactPageAccess
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _access_contact_personal_tab }
     arguments: ['@config.factory', '@user.data']
diff --git a/core/modules/contact/lib/Drupal/contact/Access/ContactPageAccess.php b/core/modules/contact/lib/Drupal/contact/Access/ContactPageAccess.php
index 182cc1a..c8d0ed9 100644
--- a/core/modules/contact/lib/Drupal/contact/Access/ContactPageAccess.php
+++ b/core/modules/contact/lib/Drupal/contact/Access/ContactPageAccess.php
@@ -7,8 +7,8 @@
 
 namespace Drupal\contact\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
 use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\user\UserDataInterface;
 use Symfony\Component\Routing\Route;
@@ -17,7 +17,7 @@
 /**
  * Access check for contact_personal_page route.
  */
-class ContactPageAccess implements StaticAccessCheckInterface {
+class ContactPageAccess implements AccessInterface {
 
   /**
    * The contact settings config object.
@@ -49,13 +49,6 @@ public function __construct(ConfigFactory $config_factory, UserDataInterface $us
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    return array('_access_contact_personal_tab');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     $contact_account = $request->attributes->get('user');
 
diff --git a/core/modules/contact/lib/Drupal/contact/Tests/ContactPersonalTest.php b/core/modules/contact/lib/Drupal/contact/Tests/ContactPersonalTest.php
index a6ca7f9..c7056a7 100644
--- a/core/modules/contact/lib/Drupal/contact/Tests/ContactPersonalTest.php
+++ b/core/modules/contact/lib/Drupal/contact/Tests/ContactPersonalTest.php
@@ -73,7 +73,8 @@ function testSendPersonalContactMessage() {
     $this->assertEqual(1, count($mails));
     $mail = $mails[0];
     $this->assertEqual($mail['to'], $this->contact_user->getEmail());
-    $this->assertEqual($mail['from'], $this->web_user->getEmail());
+    $this->assertEqual($mail['from'], \Drupal::config('system.site')->get('mail'));
+    $this->assertEqual($mail['reply-to'], $this->web_user->getEmail());
     $this->assertEqual($mail['key'], 'user_mail');
     $variables = array(
       '!site-name' => \Drupal::config('system.site')->get('name'),
diff --git a/core/modules/content_translation/content_translation.admin.inc b/core/modules/content_translation/content_translation.admin.inc
index 9f07191..a39d8f4 100644
--- a/core/modules/content_translation/content_translation.admin.inc
+++ b/core/modules/content_translation/content_translation.admin.inc
@@ -76,7 +76,7 @@ function _content_translation_form_language_content_settings_form_alter(array &$
   $dependent_options_settings = array();
   $entity_manager = Drupal::entityManager();
   foreach ($form['#labels'] as $entity_type => $label) {
-    $entity_info = entity_get_info($entity_type);
+    $entity_info = \Drupal::entityManager()->getDefinition($entity_type);
     foreach (entity_get_bundles($entity_type) as $bundle => $bundle_info) {
       // Here we do not want the widget to be altered and hold also the "Enable
       // translation" checkbox, which would be redundant. Hence we add this key
@@ -85,7 +85,7 @@ function _content_translation_form_language_content_settings_form_alter(array &$
 
       // Only show the checkbox to enable translation if the bundles in the
       // entity might have fields and if there are fields to translate.
-      if (!empty($entity_info['fieldable'])) {
+      if ($entity_info->isFieldable()) {
         $fields = $entity_manager->getFieldDefinitions($entity_type, $bundle);
         if ($fields) {
           $form['settings'][$entity_type][$bundle]['translatable'] = array(
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 0d476cb..7618840 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -78,23 +78,28 @@ function content_translation_language_types_info_alter(array &$language_types) {
  */
 function content_translation_entity_info_alter(array &$entity_info) {
   // Provide defaults for translation info.
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
   foreach ($entity_info as $entity_type => &$info) {
-    if (!empty($info['translatable'])) {
-      $info['controllers'] += array('translation' => 'Drupal\content_translation\ContentTranslationController');
+    if ($info->isTranslatable()) {
+      if (!$info->hasController('translation')) {
+        $info->setController('translation', 'Drupal\content_translation\ContentTranslationController');
+      }
 
-      if (!isset($info['translation']['content_translation'])) {
-        $info['translation']['content_translation'] = array();
+      $translation = $info->get('translation');
+      if (!$translation || !isset($translation['content_translation'])) {
+        $translation['content_translation'] = array();
       }
 
-      if (!empty($info['links']['canonical'])) {
+      if ($info->hasLinkTemplate('canonical')) {
         // Provide default route names for the translation paths.
-        $info['links'] += array(
-          'drupal:content-translation-overview' => "content_translation.translation_overview_$entity_type",
-        );
-        $info['translation']['content_translation'] += array(
+        if (!$info->hasLinkTemplate('drupal:content-translation-overview')) {
+          $info->setLinkTemplate('drupal:content-translation-overview', "content_translation.translation_overview_$entity_type");
+        }
+        $translation['content_translation'] += array(
           'access_callback' => 'content_translation_translate_access',
         );
       }
+      $info->set('translation', $translation);
     }
   }
 }
@@ -163,13 +168,14 @@ function content_translation_menu() {
   $items = array();
 
   // Create tabs for all possible entity types.
-  foreach (entity_get_info() as $entity_type => $info) {
+  foreach (\Drupal::entityManager()->getDefinitions() as $entity_type => $info) {
     // Provide the translation UI only for enabled types.
     if (content_translation_enabled($entity_type)) {
-      $path = _content_translation_link_to_router_path($entity_type, $info['links']['canonical']);
+      $path = _content_translation_link_to_router_path($entity_type, $info->getLinkTemplate('canonical'));
       $entity_position = count(explode('/', $path)) - 1;
       $keys = array_flip(array('load_arguments'));
-      $menu_info = array_intersect_key($info['translation']['content_translation'], $keys) + array('file' => 'content_translation.pages.inc');
+      $translation = $info->get('translation');
+      $menu_info = array_intersect_key($translation['content_translation'], $keys) + array('file' => 'content_translation.pages.inc');
       $item = array();
 
       // Plugin annotations cannot contain spaces, thus we need to restore them
@@ -220,9 +226,9 @@ function content_translation_menu_alter(array &$items) {
   $items['admin/config/regional/content-language']['description'] = 'Configure language and translation support for content.';
 
   // Check that the declared menu base paths are actually valid.
-  foreach (entity_get_info() as $entity_type => $info) {
+  foreach (\Drupal::entityManager()->getDefinitions() as $entity_type => $info) {
     if (content_translation_enabled($entity_type)) {
-      $path = _content_translation_link_to_router_path($entity_type, $info['links']['canonical']);
+      $path = _content_translation_link_to_router_path($entity_type, $info->getLinkTemplate('canonical'));
 
       // If the base path is not defined we cannot provide the translation UI
       // for this entity type. In some cases the actual base path might not have
@@ -235,7 +241,7 @@ function content_translation_menu_alter(array &$items) {
           $items["$path/translations/add/%language/%language"],
           $items["$path/translations/delete/%language"]
         );
-        $t_args = array('@entity_type' => isset($info['label']) ? $info['label'] : $entity_type);
+        $t_args = array('@entity_type' => $info->getLabel() ?: $entity_type);
         watchdog('content translation', 'The entities of type @entity_type do not define a valid base path: it will not be possible to translate them.', $t_args, WATCHDOG_WARNING);
       }
       else {
@@ -262,15 +268,6 @@ function content_translation_menu_alter(array &$items) {
 }
 
 /**
- * Implements hook_local_tasks_alter().
- */
-function content_translation_local_tasks_alter(&$local_tasks) {
-  // Alters in tab_root_id onto the content translation local task.
-  $derivative = ContentTranslationLocalTasks::create(\Drupal::getContainer(), 'content_translation.local_tasks');
-  $derivative->alterLocalTasks($local_tasks);
-}
-
-/**
  * Convert an entity canonical link to a router path.
  *
  * @param string $link
@@ -327,7 +324,7 @@ function content_translation_view_access(EntityInterface $entity, $langcode, Acc
   $entity_type = $entity->entityType();
   $info = $entity->entityInfo();
   $permission = "translate $entity_type";
-  if (!empty($info['permission_granularity']) && $info['permission_granularity'] == 'bundle') {
+  if ($info->getPermissionGranularity() == 'bundle') {
     $permission = "translate {$entity->bundle()} $entity_type";
   }
   return !empty($entity->translation[$langcode]['status']) || user_access('translate any entity', $account) || user_access($permission, $account);
@@ -512,9 +509,10 @@ function content_translation_enabled($entity_type, $bundle = NULL) {
  * @todo Move to \Drupal\content_translation\ContentTranslationManager.
  */
 function content_translation_controller($entity_type) {
-  $entity_info = entity_get_info($entity_type);
+  $entity_info = \Drupal::entityManager()->getDefinition($entity_type);
   // @todo Throw an exception if the key is missing.
-  return new $entity_info['controllers']['translation']($entity_type, $entity_info);
+  $class = $entity_info->getController('translation');
+  return new $class($entity_info);
 }
 
 /**
@@ -579,11 +577,11 @@ function content_translation_permission() {
 
   // Create a translate permission for each enabled entity type and (optionally)
   // bundle.
-  foreach (entity_get_info() as $entity_type => $info) {
-    if (!empty($info['permission_granularity'])) {
-      $t_args = array('@entity_label' => drupal_strtolower(t($info['label'])));
+  foreach (\Drupal::entityManager()->getDefinitions() as $entity_type => $info) {
+    if ($permission_granularity = $info->getPermissionGranularity()) {
+      $t_args = array('@entity_label' => $info->getLowercaseLabel());
 
-      switch ($info['permission_granularity']) {
+      switch ($permission_granularity) {
         case 'bundle':
           foreach (entity_get_bundles($entity_type) as $bundle => $bundle_info) {
             if (content_translation_enabled($entity_type, $bundle)) {
@@ -715,7 +713,7 @@ function content_translation_entity_insert(EntityInterface $entity) {
 
     $translation += array(
       'source' => '',
-      'uid' => $GLOBALS['user']->id(),
+      'uid' => \Drupal::currentUser()->id(),
       'outdated' => FALSE,
       'status' => TRUE,
       'created' => REQUEST_TIME,
@@ -773,7 +771,7 @@ function content_translation_entity_update(EntityInterface $entity) {
 function content_translation_field_extra_fields() {
   $extra = array();
 
-  foreach (entity_get_info() as $entity_type => $info) {
+  foreach (\Drupal::entityManager()->getDefinitions() as $entity_type => $info) {
     foreach (entity_get_bundles($entity_type) as $bundle => $bundle_info) {
       if (content_translation_enabled($entity_type, $bundle)) {
         $extra[$entity_type][$bundle]['form']['translation'] = array(
diff --git a/core/modules/content_translation/content_translation.pages.inc b/core/modules/content_translation/content_translation.pages.inc
index 2e5fa6b..390063d 100644
--- a/core/modules/content_translation/content_translation.pages.inc
+++ b/core/modules/content_translation/content_translation.pages.inc
@@ -191,13 +191,11 @@ function content_translation_add_page(EntityInterface $entity, Language $source
   $target = !empty($target) ? $target : language(Language::TYPE_CONTENT);
   // @todo Exploit the upcoming hook_entity_prepare() when available.
   content_translation_prepare_translation($entity, $source, $target);
-  $info = $entity->entityInfo();
-  $operation = isset($info['default_operation']) ? $info['default_operation'] : 'default';
   $form_state['langcode'] = $target->id;
   $form_state['content_translation']['source'] = $source;
   $form_state['content_translation']['target'] = $target;
   $form_state['content_translation']['translation_form'] = !$entity->access('update');
-  return \Drupal::entityManager()->getForm($entity, $operation, $form_state);
+  return \Drupal::entityManager()->getForm($entity, 'default', $form_state);
 }
 
 /**
@@ -216,11 +214,9 @@ function content_translation_add_page(EntityInterface $entity, Language $source
  */
 function content_translation_edit_page(EntityInterface $entity, Language $language = NULL) {
   $language = !empty($language) ? $language : language(Language::TYPE_CONTENT);
-  $info = $entity->entityInfo();
-  $operation = isset($info['default_operation']) ? $info['default_operation'] : 'default';
   $form_state['langcode'] = $language->id;
   $form_state['content_translation']['translation_form'] = TRUE;
-  return \Drupal::entityManager()->getForm($entity, $operation, $form_state);
+  return \Drupal::entityManager()->getForm($entity, 'default', $form_state);
 }
 
 /**
diff --git a/core/modules/content_translation/content_translation.services.yml b/core/modules/content_translation/content_translation.services.yml
index 6b3dcbd..da2acb2 100644
--- a/core/modules/content_translation/content_translation.services.yml
+++ b/core/modules/content_translation/content_translation.services.yml
@@ -13,13 +13,13 @@ services:
     class: Drupal\content_translation\Access\ContentTranslationOverviewAccess
     arguments: ['@entity.manager']
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _access_content_translation_overview }
 
   content_translation.manage_access:
     class: Drupal\content_translation\Access\ContentTranslationManageAccessCheck
     arguments: ['@entity.manager']
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _access_content_translation_manage }
 
   content_translation.manager:
     class: Drupal\content_translation\ContentTranslationManager
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationManageAccessCheck.php b/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationManageAccessCheck.php
index 16cc31a..7bf8ea0 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationManageAccessCheck.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationManageAccessCheck.php
@@ -7,9 +7,9 @@
 
 namespace Drupal\content_translation\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Language\Language;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -17,7 +17,7 @@
 /**
  * Access check for entity translation CRUD operation.
  */
-class ContentTranslationManageAccessCheck implements StaticAccessCheckInterface {
+class ContentTranslationManageAccessCheck implements AccessInterface {
 
   /**
    * The entity type manager.
@@ -39,20 +39,13 @@ public function __construct(EntityManagerInterface $manager) {
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    return array('_access_content_translation_manage');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     $entity_type = $request->attributes->get('_entity_type');
     if ($entity = $request->attributes->get($entity_type)) {
       $route_requirements = $route->getRequirements();
       $operation = $route_requirements['_access_content_translation_manage'];
       $controller_class = $this->entityManager->getControllerClass($entity_type, 'translation');
-      $controller = new $controller_class($entity_type, $entity->entityInfo());
+      $controller = new $controller_class($entity->entityInfo());
 
       // Load translation.
       $translations = $entity->getTranslationLanguages();
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationOverviewAccess.php b/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationOverviewAccess.php
index 5086e79..0676af5 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationOverviewAccess.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationOverviewAccess.php
@@ -7,8 +7,8 @@
 
 namespace Drupal\content_translation\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -16,7 +16,7 @@
 /**
  * Access check for entity translation overview.
  */
-class ContentTranslationOverviewAccess implements StaticAccessCheckInterface {
+class ContentTranslationOverviewAccess implements AccessInterface {
 
   /**
    * The entity type manager.
@@ -38,13 +38,6 @@ public function __construct(EntityManagerInterface $manager) {
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    return array('_access_content_translation_overview');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     $entity_type = $request->attributes->get('_entity_type');
     if ($entity = $request->attributes->get($entity_type)) {
@@ -52,15 +45,16 @@ public function access(Route $route, Request $request, AccountInterface $account
       $bundle = $entity->bundle();
 
       // Get entity access callback.
-      $definitions = $this->entityManager->getDefinitions();
-      $access_callback = $definitions[$entity_type]['translation']['content_translation']['access_callback'];
+      $definition = $this->entityManager->getDefinition($entity_type);
+      $translation = $definition->get('translation');
+      $access_callback = $translation['content_translation']['access_callback'];
       if (call_user_func($access_callback, $entity)) {
         return static::ALLOW;
       }
 
       // Check per entity permission.
       $permission = "translate {$entity_type}";
-      if ($definitions[$entity_type]['permission_granularity'] == 'bundle') {
+      if ($definition->getPermissionGranularity() == 'bundle') {
         $permission = "translate {$bundle} {$entity_type}";
       }
       if ($account->hasPermission($permission)) {
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationController.php b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationController.php
index 6d14c1b..e99c9b0 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationController.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationController.php
@@ -25,20 +25,18 @@ class ContentTranslationController implements ContentTranslationControllerInterf
   /**
    * The entity info of the entity being translated.
    *
-   * @var array
+   * @var \Drupal\Core\Entity\EntityTypeInterface
    */
   protected $entityInfo;
 
   /**
    * Initializes an instance of the content translation controller.
    *
-   * @param string $entity_type
-   *   The type of the entity being translated.
-   * @param array $entity_info
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
    *   The info array of the given entity type.
    */
-  public function __construct($entity_type, $entity_info) {
-    $this->entityType = $entity_type;
+  public function __construct($entity_info) {
+    $this->entityType = $entity_info->id();
     $this->entityInfo = $entity_info;
   }
 
@@ -63,8 +61,8 @@ public function getTranslationAccess(EntityInterface $entity, $op) {
     $translate_permission = TRUE;
     // If no permission granularity is defined this entity type does not need an
     // explicit translate permission.
-    if (!user_access('translate any entity') && !empty($info['permission_granularity'])) {
-      $translate_permission = user_access($info['permission_granularity'] == 'bundle' ? "translate {$entity->bundle()} {$entity->entityType()}" : "translate {$entity->entityType()}");
+    if (!user_access('translate any entity') && $permission_granularity = $info->getPermissionGranularity()) {
+      $translate_permission = user_access($permission_granularity == 'bundle' ? "translate {$entity->bundle()} {$entity->entityType()}" : "translate {$entity->entityType()}");
     }
     return $translate_permission && user_access("$op content translations");
   }
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationManager.php b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationManager.php
index 1f98147..b575310 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationManager.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationManager.php
@@ -36,7 +36,7 @@ public function __construct(EntityManagerInterface $manager) {
    */
   public function isSupported($entity_type) {
     $info = $this->entityManager->getDefinition($entity_type);
-    return !empty($info['translatable']) && !empty($info['links']['drupal:content-translation-overview']);
+    return $info->isTranslatable() && $info->hasLinkTemplate('drupal:content-translation-overview');
   }
 
   /**
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationManagerInterface.php b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationManagerInterface.php
index 92ff0fd..d3aa162 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationManagerInterface.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationManagerInterface.php
@@ -15,7 +15,7 @@
   /**
    * Gets the entity types that support content translation.
    *
-   * @return array
+   * @return \Drupal\Core\Entity\EntityTypeInterface[]
    *   An array of entity types that support content translation.
    */
   public function getSupportedEntityTypes();
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Form/ContentTranslationDeleteForm.php b/core/modules/content_translation/lib/Drupal/content_translation/Form/ContentTranslationDeleteForm.php
index 0ddbfc6..5d33cdf 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Form/ContentTranslationDeleteForm.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Form/ContentTranslationDeleteForm.php
@@ -63,9 +63,8 @@ public function getQuestion() {
    * {@inheritdoc}
    */
   public function getCancelRoute() {
-    $entity_info = $this->entity->entityInfo();
     return array(
-      'route_name' => $entity_info['links']['drupal:content-translation-overview'],
+      'route_name' => $this->entity->entityInfo()->getLinkTemplate('drupal:content-translation-overview'),
       'route_parameters' => array(
         $this->entity->entityType() => $this->entity->id(),
       ),
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationContextualLinks.php b/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationContextualLinks.php
index 98956ab..6238799 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationContextualLinks.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationContextualLinks.php
@@ -8,7 +8,7 @@
 namespace Drupal\content_translation\Plugin\Derivative;
 
 use Drupal\Component\Plugin\Derivative\DerivativeBase;
-use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeInterface;
 use Drupal\Core\Routing\RouteProviderInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -23,17 +23,17 @@ class ContentTranslationContextualLinks extends DerivativeBase implements Contai
   /**
    * The entity manager.
    *
-   * @var \Drupal\Core\Entity\EntityManager
+   * @var \Drupal\Core\Entity\EntityManagerInterface
    */
   protected $entityManager;
 
   /**
    * Constructs a new ContentTranslationContextualLinks.
    *
-   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
    */
-  public function __construct(EntityManager $entity_manager) {
+  public function __construct(EntityManagerInterface $entity_manager) {
     $this->entityManager = $entity_manager;
   }
 
@@ -52,9 +52,9 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   public function getDerivativeDefinitions(array $base_plugin_definition) {
     // Create contextual links for all possible entity types.
     foreach ($this->entityManager->getDefinitions() as $entity_type => $entity_info) {
-      if ($entity_info['translatable'] && isset($entity_info['translation'])) {
+      if ($entity_info->isTranslatable()) {
         $this->derivatives[$entity_type]['title'] = t('Translate');
-        $this->derivatives[$entity_type]['route_name'] = $entity_info['links']['drupal:content-translation-overview'];
+        $this->derivatives[$entity_type]['route_name'] = $entity_info->getLinkTemplate('drupal:content-translation-overview');
         $this->derivatives[$entity_type]['group'] = $entity_type;
       }
     }
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationLocalTasks.php b/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationLocalTasks.php
index e906fd9..fcb25f1 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationLocalTasks.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationLocalTasks.php
@@ -8,14 +8,14 @@
 namespace Drupal\content_translation\Plugin\Derivative;
 
 use Drupal\content_translation\ContentTranslationManagerInterface;
-use Drupal\Core\Menu\LocalTaskDerivativeBase;
+use Drupal\Component\Plugin\Derivative\DerivativeBase;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Provides dynamic local tasks for content translation.
  */
-class ContentTranslationLocalTasks extends LocalTaskDerivativeBase implements ContainerDerivativeInterface {
+class ContentTranslationLocalTasks extends DerivativeBase implements ContainerDerivativeInterface {
 
   /**
    * The base plugin ID
@@ -61,31 +61,16 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
     // Create tabs for all possible entity types.
     foreach ($this->contentTranslationManager->getSupportedEntityTypes() as $entity_type => $entity_info) {
       // Find the route name for the translation overview.
-      $translation_route_name = $entity_info['links']['drupal:content-translation-overview'];
+      $translation_route_name = $entity_info->getLinkTemplate('drupal:content-translation-overview');
 
       $this->derivatives[$translation_route_name] = array(
         'entity_type' => $entity_type,
         'title' => 'Translate',
         'route_name' => $translation_route_name,
+        'base_route' => $entity_info->getLinkTemplate('canonical'),
       ) + $base_plugin_definition;
     }
     return parent::getDerivativeDefinitions($base_plugin_definition);
   }
 
-  /**
-   * Alters the local tasks to find the proper tab_root_id for each task.
-   */
-  public function alterLocalTasks(array &$local_tasks) {
-    foreach ($this->contentTranslationManager->getSupportedEntityTypes() as $entity_info) {
-      // Find the route name for the entity page.
-      $entity_route_name = $entity_info['links']['canonical'];
-
-      // Find the route name for the translation overview.
-      $translation_route_name = $entity_info['links']['drupal:content-translation-overview'];
-      $translation_tab = $this->basePluginId . ':' . $translation_route_name;
-
-      $local_tasks[$translation_tab]['tab_root_id'] = $this->getPluginIdFromRoute($entity_route_name, $local_tasks);
-    }
-  }
-
 }
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Routing/ContentTranslationRouteSubscriber.php b/core/modules/content_translation/lib/Drupal/content_translation/Routing/ContentTranslationRouteSubscriber.php
index 2390090..2b18a13 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Routing/ContentTranslationRouteSubscriber.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Routing/ContentTranslationRouteSubscriber.php
@@ -41,7 +41,7 @@ public function __construct(ContentTranslationManagerInterface $content_translat
   protected function alterRoutes(RouteCollection $collection, $provider) {
     foreach ($this->contentTranslationManager->getSupportedEntityTypes() as $entity_type => $entity_info) {
       // Try to get the route from the current collection.
-      if (!$entity_route = $collection->get($entity_info['links']['canonical'])) {
+      if (!$entity_route = $collection->get($entity_info->getLinkTemplate('canonical'))) {
         continue;
       }
       $path = $entity_route->getPath() . '/translations';
@@ -67,7 +67,7 @@ protected function alterRoutes(RouteCollection $collection, $provider) {
           ),
         )
       );
-      $collection->add($entity_info['links']['drupal:content-translation-overview'], $route);
+      $collection->add($entity_info->getLinkTemplate('drupal:content-translation-overview'), $route);
 
       $route = new Route(
         $path . '/add/{source}/{target}',
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationSyncImageTest.php b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationSyncImageTest.php
index f974837..baf99d8 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationSyncImageTest.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationSyncImageTest.php
@@ -144,7 +144,7 @@ function testImageFieldSync() {
       // identifier.
       $field_values = array(
         'uri' => $this->files[$index]->uri,
-        'uid' => $GLOBALS['user']->id(),
+        'uid' => \Drupal::currentUser()->id(),
         'status' => FILE_STATUS_PERMANENT,
       );
       $file = entity_create('file', $field_values);
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationTestBase.php b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationTestBase.php
index d1485d2..6e18d0f 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationTestBase.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationTestBase.php
@@ -110,9 +110,9 @@ protected function getTranslatorPermissions() {
    * Returns the translate permissions for the current entity and bundle.
    */
   protected function getTranslatePermission() {
-    $info = entity_get_info($this->entityType);
-    if (!empty($info['permission_granularity'])) {
-      return $info['permission_granularity'] == 'bundle' ? "translate {$this->bundle} {$this->entityType}" : "translate {$this->entityType}";
+    $info = \Drupal::entityManager()->getDefinition($this->entityType);
+    if ($permission_granularity = $info->getPermissionGranularity()) {
+      return $permission_granularity == 'bundle' ? "translate {$this->bundle} {$this->entityType}" : "translate {$this->entityType}";
     }
   }
 
@@ -206,9 +206,9 @@ protected function setupTestFields() {
   protected function createEntity($values, $langcode, $bundle_name = NULL) {
     $entity_values = $values;
     $entity_values['langcode'] = $langcode;
-    $info = entity_get_info($this->entityType);
-    if (!empty($info['entity_keys']['bundle'])) {
-      $entity_values[$info['entity_keys']['bundle']] = $bundle_name ?: $this->bundle;
+    $info = \Drupal::entityManager()->getDefinition($this->entityType);
+    if ($bundle_key = $info->getKey('bundle')) {
+      $entity_values[$bundle_key] = $bundle_name ?: $this->bundle;
     }
     $controller = $this->container->get('entity.manager')->getStorageController($this->entityType);
     if (!($controller instanceof FieldableDatabaseStorageController)) {
diff --git a/core/modules/content_translation/tests/Drupal/content_translation/Tests/Menu/ContentTranslationLocalTasksTest.php b/core/modules/content_translation/tests/Drupal/content_translation/Tests/Menu/ContentTranslationLocalTasksTest.php
index e0b164b..057a9a4 100644
--- a/core/modules/content_translation/tests/Drupal/content_translation/Tests/Menu/ContentTranslationLocalTasksTest.php
+++ b/core/modules/content_translation/tests/Drupal/content_translation/Tests/Menu/ContentTranslationLocalTasksTest.php
@@ -8,7 +8,6 @@
 namespace Drupal\content_translation\Tests\Menu;
 
 use Drupal\Tests\Core\Menu\LocalTaskIntegrationTest;
-use Drupal\content_translation\Plugin\Derivative\ContentTranslationLocalTasks;;
 
 /**
  * Tests existence of block local tasks.
@@ -33,40 +32,23 @@ public function setUp() {
     );
     parent::setUp();
 
+    $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+    $entity_type->expects($this->any())
+      ->method('getLinkTemplate')
+      ->will($this->returnValueMap(array(
+        array('canonical', 'node.view'),
+        array('drupal:content-translation-overview', 'content_translation.translation_overview_node'),
+      )));
     $content_translation_manager = $this->getMock('Drupal\content_translation\ContentTranslationManagerInterface');
     $content_translation_manager->expects($this->any())
       ->method('getSupportedEntityTypes')
       ->will($this->returnValue(array(
-        'node' => array(
-          'translatable' => TRUE,
-          'links' => array(
-            'canonical' => 'node.view',
-            'drupal:content-translation-overview' => 'content_translation.translation_overview_node',
-          ),
-        ),
+        'node' => $entity_type,
       )));
     \Drupal::getContainer()->set('content_translation.manager', $content_translation_manager);
   }
 
   /**
-   * {@inheritdoc}
-   */
-  protected function getLocalTaskManager($modules, $route_name, $route_params) {
-    $manager = parent::getLocalTaskManager($modules, $route_name, $route_params);
-
-    // Duplicate content_translation_local_tasks_alter()'s code here to avoid
-    // having to load the .module file.
-    $this->moduleHandler->expects($this->once())
-      ->method('alter')
-      ->will($this->returnCallback(function ($hook, &$local_tasks) {
-          // Alters in tab_root_id onto the content translation local task.
-          $derivative = ContentTranslationLocalTasks::create(\Drupal::getContainer(), 'content_translation.local_tasks');
-          $derivative->alterLocalTasks($local_tasks);
-      }));
-    return $manager;
-  }
-
-  /**
    * Tests the block admin display local tasks.
    *
    * @dataProvider providerTestBlockAdminDisplay
diff --git a/core/modules/datetime/config/schema/datetime.schema.yml b/core/modules/datetime/config/schema/datetime.schema.yml
index 4074ff2..05e9fc8 100644
--- a/core/modules/datetime/config/schema/datetime.schema.yml
+++ b/core/modules/datetime/config/schema/datetime.schema.yml
@@ -9,15 +9,10 @@ field.datetime.settings:
       label: 'Date type'
 
 field.datetime.instance_settings:
-  type: mapping
-  label: 'Datetime settings'
-  mapping:
-    default_value:
-      type: string
-      label: 'Default date'
-    user_register_form:
-      type: boolean
-      label: 'Display on user registration form.'
+  type: sequence
+  label: 'Settings'
+  sequence:
+    - type: string
 
 field.datetime.value:
   type: sequence
diff --git a/core/modules/datetime/datetime.module b/core/modules/datetime/datetime.module
index 1f0c81e..ccf1a0c 100644
--- a/core/modules/datetime/datetime.module
+++ b/core/modules/datetime/datetime.module
@@ -168,48 +168,6 @@ function datetime_datelist_widget_validate(&$element, &$form_state) {
 }
 
 /**
- * Sets a default value for an empty date field.
- *
- * Callback for $instance['default_value_function'], as implemented by
- * Drupal\datetime\Plugin\Field\FieldWidget\DateTimeDatepicker.
- *
- * @param $entity_type
- *
- * @param $entity
- *
- * @param array $field
- *
- * @param array $instance
- *
- * @param $langcode
- *
- *
- * @return array
- *
- */
-function datetime_default_value($entity, $field, $instance, $langcode) {
-
-  $value = '';
-  $date = '';
-  if ($instance->getSetting('default_value') == 'now') {
-    // A default value should be in the format and timezone used for date
-    // storage.
-    $date = new DrupalDateTime('now', DATETIME_STORAGE_TIMEZONE);
-    $storage_format = $field->getSetting('datetime_type') == 'date' ? DATETIME_DATE_STORAGE_FORMAT: DATETIME_DATETIME_STORAGE_FORMAT;
-    $value = $date->format($storage_format);
-  }
-
-  // We only provide a default value for the first item, as do all fields.
-  // Otherwise, there is no way to clear out unwanted values on multiple value
-  // fields.
-  $item = array();
-  $item[0]['value'] = $value;
-  $item[0]['date'] = $date;
-
-  return $item;
-}
-
-/**
  * Sets a consistent time on a date without time.
  *
  * The default time for a date without time can be anything, so long as it is
diff --git a/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldType/DateTimeFieldItemList.php b/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldType/DateTimeFieldItemList.php
new file mode 100644
index 0000000..46a5625
--- /dev/null
+++ b/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldType/DateTimeFieldItemList.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\datetime\Plugin\Field\FieldType\DateTimeFieldItemList.
+ */
+
+namespace Drupal\datetime\Plugin\Field\FieldType;
+
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Field\ConfigFieldItemList;
+
+/**
+ * Represents a configurable entity datetime field.
+ */
+class DateTimeFieldItemList extends ConfigFieldItemList {
+
+  /**
+   * Defines the default value as now.
+   */
+  const DEFAULT_VALUE_NOW = 'now';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultValuesForm(array &$form, array &$form_state) {
+    if (empty($this->getFieldDefinition()->default_value_function)) {
+      $default_value = $this->getFieldDefinition()->default_value;
+
+      $element = array(
+        '#parents' => array('default_value_input'),
+        'default_date' => array(
+          '#type' => 'select',
+          '#title' => t('Default date'),
+          '#description' => t('Set a default value for this date.'),
+          '#default_value' => isset($default_value[0]['default_date']) ? $default_value[0]['default_date'] : '',
+          '#options' => array(static::DEFAULT_VALUE_NOW => t('The current date')),
+          '#empty_value' => '',
+        )
+      );
+
+      return $element;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultValuesFormValidate(array $element, array &$form, array &$form_state) { }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultValuesFormSubmit(array $element, array &$form, array &$form_state) {
+    if ($form_state['values']['default_value_input']['default_date']) {
+      return array($form_state['values']['default_value_input']);
+    }
+    return array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultValue() {
+    $default_value = parent::getDefaultValue();
+
+    if (isset($default_value[0]['default_date']) && $default_value[0]['default_date'] == static::DEFAULT_VALUE_NOW) {
+      // A default value should be in the format and timezone used for date
+      // storage.
+      $date = new DrupalDateTime('now', DATETIME_STORAGE_TIMEZONE);
+      $storage_format = $this->getFieldDefinition()->getSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE ? DATETIME_DATE_STORAGE_FORMAT: DATETIME_DATETIME_STORAGE_FORMAT;
+      $value = $date->format($storage_format);
+      // We only provide a default value for the first item, as do all fields.
+      // Otherwise, there is no way to clear out unwanted values on multiple value
+      // fields.
+      $default_value =  array(
+        array(
+          'value' => $value,
+          'date' => $date,
+        )
+      );
+    }
+    return $default_value;
+  }
+
+}
diff --git a/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldType/DateTimeItem.php b/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldType/DateTimeItem.php
index 3fbcca1..dc2c201 100644
--- a/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldType/DateTimeItem.php
+++ b/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldType/DateTimeItem.php
@@ -23,16 +23,24 @@
  *   settings = {
  *     "datetime_type" = "datetime"
  *   },
- *   instance_settings = {
- *     "default_value" = "now"
- *   },
  *   default_widget = "datetime_default",
- *   default_formatter = "datetime_default"
+ *   default_formatter = "datetime_default",
+ *   list_class = "\Drupal\datetime\Plugin\Field\FieldType\DateTimeFieldItemList"
  * )
  */
 class DateTimeItem extends ConfigFieldItemBase implements PrepareCacheInterface {
 
   /**
+   * Value for the 'datetime_type' setting: store only a date.
+   */
+  const DATETIME_TYPE_DATE = 'date';
+
+  /**
+   * Value for the 'datetime_type' setting: store a date and time.
+   */
+  const DATETIME_TYPE_DATETIME = 'datetime';
+
+  /**
    * Field definitions of the contained properties.
    *
    * @var array
@@ -89,8 +97,8 @@ public function settingsForm(array $form, array &$form_state, $has_data) {
       '#description' => t('Choose the type of date to create.'),
       '#default_value' => $this->getFieldSetting('datetime_type'),
       '#options' => array(
-        'datetime' => t('Date and time'),
-        'date' => t('Date only'),
+        static::DATETIME_TYPE_DATETIME => t('Date and time'),
+        static::DATETIME_TYPE_DATE => t('Date only'),
       ),
     );
 
@@ -100,24 +108,6 @@ public function settingsForm(array $form, array &$form_state, $has_data) {
   /**
    * {@inheritdoc}
    */
-  public function instanceSettingsForm(array $form, array &$form_state) {
-    $element = array();
-
-    $element['default_value'] = array(
-      '#type' => 'select',
-      '#title' => t('Default date'),
-      '#description' => t('Set a default value for this date.'),
-      '#default_value' => $this->getFieldSetting('default_value'),
-      '#options' => array('blank' => t('No default value'), 'now' => t('The current date')),
-      '#weight' => 1,
-    );
-
-    return $element;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function getCacheData() {
     $data = $this->getValue();
     // The function generates a Date object for each field early so that it is
diff --git a/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldWidget/DatetimeDatelistWidget.php b/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldWidget/DatetimeDatelistWidget.php
index 1e8bf0e..cbae23c 100644
--- a/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldWidget/DatetimeDatelistWidget.php
+++ b/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldWidget/DatetimeDatelistWidget.php
@@ -8,9 +8,7 @@
 
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Field\WidgetBase;
-use Drupal\Core\Field\FieldDefinitionInterface;
-use Drupal\field\FieldInstanceInterface;
-use Drupal\datetime\DateHelper;
+use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
 
 /**
  * Plugin implementation of the 'datetime_datelist' widget.
@@ -33,29 +31,6 @@ class DateTimeDatelistWidget extends WidgetBase {
   /**
    * {@inheritdoc}
    */
-  public function __construct($plugin_id, array $plugin_definition, FieldDefinitionInterface $field_definition, array $settings) {
-    // Identify the function used to set the default value.
-    // @todo Make this work for both configurable and nonconfigurable fields:
-    //   https://drupal.org/node/1989468.
-    if ($field_definition instanceof FieldInstanceInterface) {
-      $field_definition->default_value_function = $this->defaultValueFunction();
-    }
-    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings);
-  }
-
-  /**
-   * Returns the callback used to set a date default value.
-   *
-   * @return string
-   *   The name of the callback to use when setting a default date value.
-   */
-  public function defaultValueFunction() {
-    return 'datetime_default_value';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, array &$form_state) {
     $date_order = $this->getSetting('date_order');
     $time_type = $this->getSetting('time_type');
@@ -74,7 +49,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
 
     // Identify the type of date and time elements to use.
     switch ($this->getFieldSetting('datetime_type')) {
-      case 'date':
+      case DateTimeItem::DATETIME_TYPE_DATE:
         $storage_format = DATETIME_DATE_STORAGE_FORMAT;
         break;
 
diff --git a/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldWidget/DatetimeDefaultWidget.php b/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldWidget/DatetimeDefaultWidget.php
index 7f9708a..9389a71 100644
--- a/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldWidget/DatetimeDefaultWidget.php
+++ b/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldWidget/DatetimeDefaultWidget.php
@@ -9,7 +9,7 @@
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Field\WidgetBase;
 use Drupal\Core\Field\FieldDefinitionInterface;
-use Drupal\field\FieldInstanceInterface;
+use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
 
 /**
  * Plugin implementation of the 'datetime_default' widget.
@@ -35,12 +35,6 @@ class DateTimeDefaultWidget extends WidgetBase {
    * {@inheritdoc}
    */
   public function __construct($plugin_id, array $plugin_definition, FieldDefinitionInterface $field_definition, array $settings) {
-    // Identify the function used to set the default value.
-    // @todo Make this work for both configurable and nonconfigurable fields:
-    //   https://drupal.org/node/1989468.
-    if ($field_definition instanceof FieldInstanceInterface) {
-      $field_definition->default_value_function = $this->defaultValueFunction();
-    }
     parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings);
 
     // @todo Inject this once https://drupal.org/node/2035317 is in.
@@ -48,16 +42,6 @@ public function __construct($plugin_id, array $plugin_definition, FieldDefinitio
   }
 
   /**
-   * Return the callback used to set a date default value.
-   *
-   * @return string
-   *   The name of the callback to use when setting a default date value.
-   */
-  public function defaultValueFunction() {
-    return 'datetime_default_value';
-  }
-
-  /**
    * {@inheritdoc}
    */
   public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, array &$form_state) {
@@ -75,7 +59,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
 
     // Identify the type of date and time elements to use.
     switch ($this->getFieldSetting('datetime_type')) {
-      case 'date':
+      case DateTimeItem::DATETIME_TYPE_DATE:
         $date_type = 'date';
         $time_type = 'none';
         $date_format = $this->dateStorage->load('html_date')->getPattern($format_type);
@@ -117,7 +101,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       // The date was created and verified during field_load(), so it is safe to
       // use without further inspection.
       $date->setTimezone(new \DateTimeZone($element['value']['#date_timezone']));
-      if ($this->getFieldSetting('datetime_type') == 'date') {
+      if ($this->getFieldSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE) {
         // A date without time will pick up the current time, use the default
         // time.
         datetime_date_default_time($date);
diff --git a/core/modules/datetime/lib/Drupal/datetime/Tests/DatetimeFieldTest.php b/core/modules/datetime/lib/Drupal/datetime/Tests/DateTimeFieldTest.php
similarity index 82%
rename from core/modules/datetime/lib/Drupal/datetime/Tests/DatetimeFieldTest.php
rename to core/modules/datetime/lib/Drupal/datetime/Tests/DateTimeFieldTest.php
index 5bee2e3..c2ec068 100644
--- a/core/modules/datetime/lib/Drupal/datetime/Tests/DatetimeFieldTest.php
+++ b/core/modules/datetime/lib/Drupal/datetime/Tests/DateTimeFieldTest.php
@@ -13,7 +13,7 @@
 /**
  * Tests Datetime field functionality.
  */
-class DatetimeFieldTest extends WebTestBase {
+class DateTimeFieldTest extends WebTestBase {
 
   /**
    * Modules to enable.
@@ -48,9 +48,11 @@ function setUp() {
     parent::setUp();
 
     $web_user = $this->drupalCreateUser(array(
+      'access content',
       'view test entity',
       'administer entity_test content',
       'administer content types',
+      'administer node fields',
     ));
     $this->drupalLogin($web_user);
 
@@ -66,9 +68,6 @@ function setUp() {
       'field_name' => $this->field->name,
       'entity_type' => 'entity_test',
       'bundle' => 'entity_test',
-      'settings' => array(
-        'default_value' => 'blank',
-      ),
     ));
     $this->instance->save();
 
@@ -292,40 +291,67 @@ function testDatelistWidget() {
    * Test default value functionality.
    */
   function testDefaultValue() {
+    // Create a test content type.
+    $this->drupalCreateContentType(array('type' => 'date_content'));
 
-    // Change the field to a datetime field.
-    $this->field->settings['datetime_type'] = 'datetime';
-    $this->field->save();
-    $field_name = $this->field->name;
+    // Create a field with settings to validate.
+    $field = entity_create('field_entity', array(
+      'name' => drupal_strtolower($this->randomName()),
+      'entity_type' => 'node',
+      'type' => 'datetime',
+      'settings' => array('datetime_type' => 'date'),
+    ));
+    $field->save();
 
-    // Set the default value to 'now'.
-    $this->instance->settings['default_value'] = 'now';
-    $this->instance->default_value_function = 'datetime_default_value';
-    $this->instance->save();
+    $instance = entity_create('field_instance', array(
+      'field_name' => $field->name,
+      'entity_type' => 'node',
+      'bundle' => 'date_content',
+    ));
+    $instance->save();
 
-    // Display creation form.
-    $date = new DrupalDateTime();
-    $date_format = 'Y-m-d';
-    $this->drupalGet('entity_test/add');
+    // Set now as default_value.
+    $instance_edit = array(
+      'default_value_input[default_date]' => 'now',
+    );
+    $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field->name, $instance_edit, t('Save settings'));
 
-    // See if current date is set. We cannot test for the precise time because
-    // it may be a few seconds between the time the comparison date is created
-    // and the form date, so we just test the date and that the time is not
-    // empty.
-    $this->assertFieldByName("{$field_name}[0][value][date]", $date->format($date_format), 'Date element found.');
-    $this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Time element found.');
+    // Check that default value is selected in default value form.
+    $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field->name);
+    $this->assertRaw('<option value="now" selected="selected">The current date</option>', 'The default value is selected in instance settings page');
 
-    // Set the default value to 'blank'.
-    $this->instance->settings['default_value'] = 'blank';
-    $this->instance->default_value_function = 'datetime_default_value';
-    $this->instance->save();
+    // Check if default_date has been stored successfully.
+    $config_entity = $this->container->get('config.factory')->get('field.instance.node.date_content.' . $field->name)->get();
+    $this->assertEqual($config_entity['default_value'][0]['default_date'], 'now', 'Default value has been stored succesfully');
 
-    // Display creation form.
-    $this->drupalGet('entity_test/add');
+    // Clean field_info cache in order to avoid stale cache values.
+    field_info_cache_clear();
 
-    // See that no date is set.
-    $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Date element found.');
-    $this->assertFieldByName("{$field_name}[0][value][time]", '', 'Time element found.');
+    // Create a new node to check that datetime field default value is today.
+    $new_node = entity_create('node', array('type' => 'date_content'));
+    $expected_date = new DrupalDateTime('now', DATETIME_STORAGE_TIMEZONE);
+    $this->assertEqual($new_node->get($field->name)->offsetGet(0)->value, $expected_date->format(DATETIME_DATE_STORAGE_FORMAT));
+
+    // Remove default value.
+    $instance_edit = array(
+      'default_value_input[default_date]' => '',
+    );
+    $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field->name, $instance_edit, t('Save settings'));
+
+    // Check that default value is selected in default value form.
+    $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field->name);
+    $this->assertRaw('<option value="" selected="selected">' . t('- None -') . '</option>', 'The default value is selected in instance settings page');
+
+    // Check if default_date has been stored successfully.
+    $config_entity = $this->container->get('config.factory')->get('field.instance.node.date_content.' . $field->name)->get();
+    $this->assertTrue(empty($config_entity['default_value']), 'Empty default value has been stored succesfully');
+
+    // Clean field_info cache in order to avoid stale cache values.
+    field_info_cache_clear();
+
+    // Create a new node to check that datetime field default value is today.
+    $new_node = entity_create('node', array('type' => 'date_content'));
+    $this->assertNull($new_node->get($field->name)->offsetGet(0)->value, 'Default value is not set');
   }
 
   /**
diff --git a/core/modules/edit/edit.services.yml b/core/modules/edit/edit.services.yml
index 73f7f25..be9946c 100644
--- a/core/modules/edit/edit.services.yml
+++ b/core/modules/edit/edit.services.yml
@@ -6,12 +6,12 @@ services:
     class: Drupal\edit\Access\EditEntityFieldAccessCheck
     arguments: ['@entity.manager']
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _access_edit_entity_field }
   access_check.edit.entity:
     class: Drupal\edit\Access\EditEntityAccessCheck
     arguments: ['@entity.manager']
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _access_edit_entity }
   edit.editor.selector:
     class: Drupal\edit\EditorSelector
     arguments: ['@plugin.manager.edit.editor', '@plugin.manager.field.formatter']
diff --git a/core/modules/edit/lib/Drupal/edit/Access/EditEntityAccessCheck.php b/core/modules/edit/lib/Drupal/edit/Access/EditEntityAccessCheck.php
index 07faf6e..827c06f 100644
--- a/core/modules/edit/lib/Drupal/edit/Access/EditEntityAccessCheck.php
+++ b/core/modules/edit/lib/Drupal/edit/Access/EditEntityAccessCheck.php
@@ -7,8 +7,8 @@
 
 namespace Drupal\edit\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -18,7 +18,7 @@
 /**
  * Access check for editing entities.
  */
-class EditEntityAccessCheck implements StaticAccessCheckInterface {
+class EditEntityAccessCheck implements AccessInterface {
 
   /**
    * The entity manager.
@@ -40,14 +40,6 @@ public function __construct(EntityManagerInterface $entity_manager) {
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    // @see edit.routing.yml
-    return array('_access_edit_entity');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     // @todo Request argument validation and object loading should happen
     //   elsewhere in the request processing pipeline:
diff --git a/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php b/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php
index 2dbfc45..9de7347 100644
--- a/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php
+++ b/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php
@@ -7,9 +7,8 @@
 
 namespace Drupal\edit\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
-use Drupal\edit\Access\EditEntityFieldAccessCheckInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -19,7 +18,7 @@
 /**
  * Access check for editing entity fields.
  */
-class EditEntityFieldAccessCheck implements StaticAccessCheckInterface, EditEntityFieldAccessCheckInterface {
+class EditEntityFieldAccessCheck implements AccessInterface, EditEntityFieldAccessCheckInterface {
 
   /**
    * The entity manager.
@@ -41,13 +40,6 @@ public function __construct(EntityManagerInterface $entity_manager) {
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    return array('_access_edit_entity_field');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     // @todo Request argument validation and object loading should happen
     //   elsewhere in the request processing pipeline:
diff --git a/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php b/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php
index 78ece73..1c356b2 100644
--- a/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php
+++ b/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php
@@ -119,7 +119,7 @@ protected function init(array &$form_state, EntityInterface $entity, $field_name
     if ($entity->entityType() == 'node') {
       $node_type_settings = $this->nodeTypeStorage->load($entity->bundle())->getModuleSettings('node');
       $options = (isset($node_type_settings['options'])) ? $node_type_settings['options'] : array();
-      $entity->setNewRevision(in_array('revision', $options));
+      $entity->setNewRevision(!empty($options['revision']));
       $entity->log = NULL;
     }
 
diff --git a/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php b/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
index a8cc0b1..b8c31aa 100644
--- a/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
+++ b/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
@@ -245,7 +245,9 @@ public function testUserWithPermission() {
       // then again retrieve the field form, fill it, submit it (so it ends up
       // in TempStore) and then save the entity. Now there should be two
       // revisions.
-      $this->container->get('config.factory')->get('node.type.article')->set('settings.node.options', array('status', 'revision'))->save();
+      $node_type = entity_load('node_type', 'article');
+      $node_type->settings['node']['options']['revision'] = TRUE;
+      $node_type->save();
 
       // Retrieve field form.
       $post = array('nocssjs' => 'true', 'reset' => 'true');
diff --git a/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityAccessCheckTest.php b/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityAccessCheckTest.php
index c42ae9a..0d5ca57 100644
--- a/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityAccessCheckTest.php
+++ b/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityAccessCheckTest.php
@@ -66,13 +66,6 @@ protected function setUp() {
   }
 
   /**
-   * Tests the appliesTo method for the access checker.
-   */
-  public function testAppliesTo() {
-    $this->assertEquals($this->editAccessCheck->appliesTo(), array('_access_edit_entity'), 'Access checker returned the expected appliesTo() array.');
-  }
-
-  /**
    * Provides test data for testAccess().
    *
    * @see \Drupal\edit\Tests\edit\Access\EditEntityAccessCheckTest::testAccess()
diff --git a/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityFieldAccessCheckTest.php b/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityFieldAccessCheckTest.php
index ba2e038..b9c847d 100644
--- a/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityFieldAccessCheckTest.php
+++ b/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityFieldAccessCheckTest.php
@@ -68,13 +68,6 @@ protected function setUp() {
   }
 
   /**
-   * Tests the appliesTo method for the access checker.
-   */
-  public function testAppliesTo() {
-    $this->assertEquals($this->editAccessCheck->appliesTo(), array('_access_edit_entity_field'), 'Access checker returned the expected appliesTo() array.');
-  }
-
-  /**
    * Provides test data for testAccess().
    *
    * @see \Drupal\edit\Tests\edit\Access\EditEntityFieldAccessCheckTest::testAccess()
diff --git a/core/modules/entity/entity.local_tasks.yml b/core/modules/entity/entity.local_tasks.yml
index 7c1778d..409b772 100644
--- a/core/modules/entity/entity.local_tasks.yml
+++ b/core/modules/entity/entity.local_tasks.yml
@@ -1,19 +1,19 @@
 entity.view_mode_edit:
   title: 'Edit'
   route_name: entity.view_mode_edit
-  tab_root_id: entity.view_mode_edit
+  base_route: entity.view_mode_edit
 
 entity.form_mode_edit:
   title: 'Edit'
   route_name: entity.form_mode_edit
-  tab_root_id: entity.form_mode_edit
+  base_route: entity.form_mode_edit
 
 entity.view_mode_list:
   title: List
   route_name: entity.view_mode_list
-  tab_root_id: entity.view_mode_list
+  base_route: entity.view_mode_list
 
 entity.form_mode_list:
   title: List
   route_name: entity.form_mode_list
-  tab_root_id: entity.form_mode_list
+  base_route: entity.form_mode_list
diff --git a/core/modules/entity/entity.module b/core/modules/entity/entity.module
index e838be5..7383920 100644
--- a/core/modules/entity/entity.module
+++ b/core/modules/entity/entity.module
@@ -100,11 +100,11 @@ function entity_menu() {
  */
 function entity_entity_bundle_rename($entity_type, $bundle_old, $bundle_new) {
   // Rename entity displays.
-  $entity_info = entity_get_info('entity_display');
+  $entity_info = \Drupal::entityManager()->getDefinition('entity_display');
   if ($bundle_old !== $bundle_new) {
-    $ids = config_get_storage_names_with_prefix('entity.display.' . $entity_type . '.' . $bundle_old);
+    $ids = config_get_storage_names_with_prefix('entity.display.' . $entity_type . '.' . $bundle_old . '.');
     foreach ($ids as $id) {
-      $id = ConfigStorageController::getIDFromConfigName($id, $entity_info['config_prefix']);
+      $id = ConfigStorageController::getIDFromConfigName($id, $entity_info->getConfigPrefix());
       $display = entity_load('entity_display', $id);
       $new_id = $entity_type . '.' . $bundle_new . '.' . $display->mode;
       $display->id = $new_id;
@@ -114,11 +114,11 @@ function entity_entity_bundle_rename($entity_type, $bundle_old, $bundle_new) {
   }
 
   // Rename entity form displays.
-  $entity_info = entity_get_info('entity_form_display');
+  $entity_info = \Drupal::entityManager()->getDefinition('entity_form_display');
   if ($bundle_old !== $bundle_new) {
-    $ids = config_get_storage_names_with_prefix('entity.form_display.' . $entity_type . '.' . $bundle_old);
+    $ids = config_get_storage_names_with_prefix('entity.form_display.' . $entity_type . '.' . $bundle_old . '.');
     foreach ($ids as $id) {
-      $id = ConfigStorageController::getIDFromConfigName($id, $entity_info['config_prefix']);
+      $id = ConfigStorageController::getIDFromConfigName($id, $entity_info->getConfigPrefix());
       $form_display = entity_load('entity_form_display', $id);
       $new_id = $entity_type . '.' . $bundle_new . '.' . $form_display->mode;
       $form_display->id = $new_id;
@@ -133,18 +133,18 @@ function entity_entity_bundle_rename($entity_type, $bundle_old, $bundle_new) {
  */
 function entity_entity_bundle_delete($entity_type, $bundle) {
   // Remove entity displays of the deleted bundle.
-  $entity_info = entity_get_info('entity_display');
-  $ids = config_get_storage_names_with_prefix('entity.display.' . $entity_type . '.' . $bundle);
+  $entity_info = \Drupal::entityManager()->getDefinition('entity_display');
+  $ids = config_get_storage_names_with_prefix('entity.display.' . $entity_type . '.' . $bundle . '.');
   foreach ($ids as &$id) {
-    $id = ConfigStorageController::getIDFromConfigName($id, $entity_info['config_prefix']);
+    $id = ConfigStorageController::getIDFromConfigName($id, $entity_info->getConfigPrefix());
   }
   entity_delete_multiple('entity_display', $ids);
 
   // Remove entity form displays of the deleted bundle.
-  $entity_info = entity_get_info('entity_form_display');
-  $ids = config_get_storage_names_with_prefix('entity.form_display.' . $entity_type . '.' . $bundle);
+  $entity_info = \Drupal::entityManager()->getDefinition('entity_form_display');
+  $ids = config_get_storage_names_with_prefix('entity.form_display.' . $entity_type . '.' . $bundle . '.');
   foreach ($ids as &$id) {
-    $id = ConfigStorageController::getIDFromConfigName($id, $entity_info['config_prefix']);
+    $id = ConfigStorageController::getIDFromConfigName($id, $entity_info->getConfigPrefix());
   }
   entity_delete_multiple('entity_form_display', $ids);
 }
@@ -156,7 +156,7 @@ function entity_module_preuninstall($module) {
   // Clean up all entity bundles (including field instances) of every entity
   // type provided by the module that is being uninstalled.
   foreach (\Drupal::entityManager()->getDefinitions() as $entity_type => $entity_info) {
-    if ($entity_info['provider'] == $module) {
+    if ($entity_info->getProvider() == $module) {
       foreach (array_keys(entity_get_bundles($entity_type)) as $bundle) {
         entity_invoke_bundle_hook('delete', $entity_type, $bundle);
       }
diff --git a/core/modules/entity/lib/Drupal/entity/Controller/EntityDisplayModeController.php b/core/modules/entity/lib/Drupal/entity/Controller/EntityDisplayModeController.php
index eb0e641..e0f1d0f 100644
--- a/core/modules/entity/lib/Drupal/entity/Controller/EntityDisplayModeController.php
+++ b/core/modules/entity/lib/Drupal/entity/Controller/EntityDisplayModeController.php
@@ -7,8 +7,8 @@
 
 namespace Drupal\entity\Controller;
 
-use Drupal\Component\Plugin\PluginManagerInterface;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -19,17 +19,17 @@ class EntityDisplayModeController implements ContainerInjectionInterface {
   /**
    * The entity manager.
    *
-   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   * @var \Drupal\Core\Entity\EntityManagerInterface
    */
   protected $entityManager;
 
   /**
    * Constructs a new EntityDisplayModeFormBase.
    *
-   * @param \Drupal\Component\Plugin\PluginManagerInterface $entity_manager
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
    */
-  public function __construct(PluginManagerInterface $entity_manager) {
+  public function __construct(EntityManagerInterface $entity_manager) {
     $this->entityManager = $entity_manager;
   }
 
@@ -51,9 +51,9 @@ public static function create(ContainerInterface $container) {
   public function viewModeTypeSelection() {
     $entity_types = array();
     foreach ($this->entityManager->getDefinitions() as $entity_type => $entity_info) {
-      if ($entity_info['fieldable'] && isset($entity_info['controllers']['view_builder'])) {
+      if ($entity_info->isFieldable() && $entity_info->hasController('view_builder')) {
         $entity_types[$entity_type] = array(
-          'title' => $entity_info['label'],
+          'title' => $entity_info->getLabel(),
           'href' => 'admin/structure/display-modes/view/add/' . $entity_type,
           'localized_options' => array(),
         );
@@ -74,9 +74,9 @@ public function viewModeTypeSelection() {
   public function formModeTypeSelection() {
     $entity_types = array();
     foreach ($this->entityManager->getDefinitions() as $entity_type => $entity_info) {
-      if ($entity_info['fieldable'] && isset($entity_info['controllers']['form'])) {
+      if ($entity_info->isFieldable() && $entity_info->hasController('form')) {
         $entity_types[$entity_type] = array(
-          'title' => $entity_info['label'],
+          'title' => $entity_info->getLabel(),
           'href' => 'admin/structure/display-modes/form/add/' . $entity_type,
           'localized_options' => array(),
         );
diff --git a/core/modules/entity/lib/Drupal/entity/EntityDisplayModeListController.php b/core/modules/entity/lib/Drupal/entity/EntityDisplayModeListController.php
index 835c1c6..8946534 100644
--- a/core/modules/entity/lib/Drupal/entity/EntityDisplayModeListController.php
+++ b/core/modules/entity/lib/Drupal/entity/EntityDisplayModeListController.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -21,26 +22,24 @@ class EntityDisplayModeListController extends ConfigEntityListController {
   /**
    * The entity info for all entity types.
    *
-   * @var array
+   * @var \Drupal\Core\Entity\EntityTypeInterface[]
    */
   protected $entityInfoComplete;
 
   /**
    * Constructs a new EntityListController object.
    *
-   * @param string $entity_type
-   *   The type of entity to be listed.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
-   * @param array $entity_info_complete
+   * @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_info_complete
    *   The entity info for all entity types.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, array $entity_info_complete) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, array $entity_info_complete) {
+    parent::__construct($entity_info, $storage, $module_handler);
 
     $this->entityInfoComplete = $entity_info_complete;
   }
@@ -48,12 +47,11 @@ public function __construct($entity_type, array $entity_info, EntityStorageContr
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     $entity_manager = $container->get('entity.manager');
     return new static(
-      $entity_type,
       $entity_info,
-      $entity_manager->getStorageController($entity_type),
+      $entity_manager->getStorageController($entity_info->id()),
       $container->get('module_handler'),
       $entity_manager->getDefinitions()
     );
@@ -97,12 +95,12 @@ public function render() {
       }
 
       // Filter entities
-      if ($this->entityInfoComplete[$entity_type]['fieldable'] && !$this->isValidEntity($entity_type)) {
+      if ($this->entityInfoComplete[$entity_type]->isFieldable() && !$this->isValidEntity($entity_type)) {
         continue;
       }
 
       $table = array(
-        '#prefix' => '<h2>' . $this->entityInfoComplete[$entity_type]['label'] . '</h2>',
+        '#prefix' => '<h2>' . $this->entityInfoComplete[$entity_type]->getLabel() . '</h2>',
         '#type' => 'table',
         '#header' => $this->buildHeader(),
         '#rows' => array(),
@@ -123,7 +121,7 @@ public function render() {
         'data' => array(
           '#type' => 'link',
           '#href' => "admin/structure/display-modes/$short_type/add/$entity_type",
-          '#title' => t('Add new %label @entity-type', array('%label' => $this->entityInfoComplete[$entity_type]['label'], '@entity-type' => strtolower($this->entityInfo['label']))),
+          '#title' => t('Add new %label @entity-type', array('%label' => $this->entityInfoComplete[$entity_type]->getLabel(), '@entity-type' => $this->entityInfo->getLowercaseLabel())),
           '#options' => array(
             'html' => TRUE,
           ),
@@ -146,7 +144,7 @@ public function render() {
    *   doesn't has the correct controller.
    */
   protected function isValidEntity($entity_type) {
-    return isset($this->entityInfoComplete[$entity_type]['controllers']['view_builder']);
+    return $this->entityInfoComplete[$entity_type]->hasController('view_builder');
   }
 
 }
diff --git a/core/modules/entity/lib/Drupal/entity/EntityFormModeListController.php b/core/modules/entity/lib/Drupal/entity/EntityFormModeListController.php
index 79417ff..0ae68f1 100644
--- a/core/modules/entity/lib/Drupal/entity/EntityFormModeListController.php
+++ b/core/modules/entity/lib/Drupal/entity/EntityFormModeListController.php
@@ -23,7 +23,7 @@ class EntityFormModeListController extends EntityDisplayModeListController {
    *   doesn't has the correct controller.
    */
   protected function isValidEntity($entity_type) {
-    return isset($this->entityInfoComplete[$entity_type]['controllers']['form']);
+    return $this->entityInfoComplete[$entity_type]->hasController('form');
   }
 
 }
diff --git a/core/modules/entity/lib/Drupal/entity/Form/EntityDisplayModeAddForm.php b/core/modules/entity/lib/Drupal/entity/Form/EntityDisplayModeAddForm.php
index 2088273..0d27cec 100644
--- a/core/modules/entity/lib/Drupal/entity/Form/EntityDisplayModeAddForm.php
+++ b/core/modules/entity/lib/Drupal/entity/Form/EntityDisplayModeAddForm.php
@@ -26,7 +26,7 @@ public function buildForm(array $form, array &$form_state, $entity_type = NULL)
     $this->entityType = $entity_type;
     $form = parent::buildForm($form, $form_state);
     $definition = $this->entityManager->getDefinition($this->entityType);
-    $form['#title'] = $this->t('Add new %label @entity-type', array('%label' => $definition['label'], '@entity-type' => strtolower($this->entityInfo['label'])));
+    $form['#title'] = $this->t('Add new %label @entity-type', array('%label' => $definition->getLabel(), '@entity-type' => $this->entityInfo->getLowercaseLabel()));
     return $form;
   }
 
@@ -44,7 +44,7 @@ public function validate(array $form, array &$form_state) {
    */
   protected function prepareEntity() {
     $definition = $this->entityManager->getDefinition($this->entityType);
-    if (!$definition['fieldable'] || !isset($definition['controllers']['view_builder'])) {
+    if (!$definition->isFieldable() || !$definition->hasController('view_builder')) {
       throw new NotFoundHttpException();
     }
 
diff --git a/core/modules/entity/lib/Drupal/entity/Form/EntityDisplayModeDeleteForm.php b/core/modules/entity/lib/Drupal/entity/Form/EntityDisplayModeDeleteForm.php
index beee9d7..d1bdd51 100644
--- a/core/modules/entity/lib/Drupal/entity/Form/EntityDisplayModeDeleteForm.php
+++ b/core/modules/entity/lib/Drupal/entity/Form/EntityDisplayModeDeleteForm.php
@@ -28,7 +28,7 @@ public function getCancelRoute() {
    */
   public function getQuestion() {
     $entity_info = $this->entity->entityInfo();
-    return t('Are you sure you want to delete the %label @entity-type?', array('%label' => $this->entity->label(), '@entity-type' => strtolower($entity_info['label'])));
+    return t('Are you sure you want to delete the %label @entity-type?', array('%label' => $this->entity->label(), '@entity-type' => $entity_info->getLowercaseLabel()));
   }
 
   /**
@@ -36,7 +36,7 @@ public function getQuestion() {
    */
   public function getDescription() {
     $entity_info = $this->entity->entityInfo();
-    return t('Deleting a @entity-type will cause any output still requesting to use that @entity-type to use the default display settings.', array('@entity-type' => strtolower($entity_info['label'])));
+    return t('Deleting a @entity-type will cause any output still requesting to use that @entity-type to use the default display settings.', array('@entity-type' => $entity_info->getLowercaseLabel()));
   }
 
   /**
@@ -53,7 +53,7 @@ public function submit(array $form, array &$form_state) {
     parent::submit($form, $form_state);
 
     $entity_info = $this->entity->entityInfo();
-    drupal_set_message(t('Deleted the %label @entity-type.', array('%label' => $this->entity->label(), '@entity-type' => strtolower($entity_info['label']))));
+    drupal_set_message(t('Deleted the %label @entity-type.', array('%label' => $this->entity->label(), '@entity-type' => $entity_info->getLowercaseLabel())));
     $this->entity->delete();
     entity_info_cache_clear();
     $form_state['redirect_route']['route_name'] = 'entity.' . $this->entity->entityType() . '_list';
diff --git a/core/modules/entity/lib/Drupal/entity/Form/EntityDisplayModeFormBase.php b/core/modules/entity/lib/Drupal/entity/Form/EntityDisplayModeFormBase.php
index dc96922..2f41ab5 100644
--- a/core/modules/entity/lib/Drupal/entity/Form/EntityDisplayModeFormBase.php
+++ b/core/modules/entity/lib/Drupal/entity/Form/EntityDisplayModeFormBase.php
@@ -27,7 +27,7 @@
   /**
    * The entity type definition.
    *
-   * @var array
+   * @var \Drupal\Core\Entity\EntityTypeInterface
    */
   protected $entityInfo;
 
@@ -119,7 +119,7 @@ public function exists($entity_id, array $element, array $form_state) {
    * {@inheritdoc}
    */
   public function save(array $form, array &$form_state) {
-    drupal_set_message(t('Saved the %label @entity-type.', array('%label' => $this->entity->label(), '@entity-type' => strtolower($this->entityInfo['label']))));
+    drupal_set_message(t('Saved the %label @entity-type.', array('%label' => $this->entity->label(), '@entity-type' => $this->entityInfo->getLowercaseLabel())));
     $this->entity->save();
     entity_info_cache_clear();
     $form_state['redirect_route']['route_name'] = 'entity.' . $this->entity->entityType() . '_list';
diff --git a/core/modules/entity/lib/Drupal/entity/Form/EntityFormModeAddForm.php b/core/modules/entity/lib/Drupal/entity/Form/EntityFormModeAddForm.php
index 84432e4..ce27c0f 100644
--- a/core/modules/entity/lib/Drupal/entity/Form/EntityFormModeAddForm.php
+++ b/core/modules/entity/lib/Drupal/entity/Form/EntityFormModeAddForm.php
@@ -19,7 +19,7 @@ class EntityFormModeAddForm extends EntityDisplayModeAddForm {
    */
   protected function prepareEntity() {
     $definition = $this->entityManager->getDefinition($this->entityType);
-    if (!$definition['fieldable'] || !isset($definition['controllers']['form'])) {
+    if (!$definition->isFieldable() || !$definition->hasController('form')) {
       throw new NotFoundHttpException();
     }
 
diff --git a/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php b/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php
index 5fe5f89..bc4e144 100644
--- a/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php
+++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php
@@ -14,7 +14,7 @@
  */
 class EntityDisplayTest extends DrupalUnitTestBase {
 
-  public static $modules = array('entity', 'field', 'entity_test');
+  public static $modules = array('entity', 'field', 'entity_test', 'user');
 
   public static function getInfo() {
     return array(
diff --git a/core/modules/entity/lib/Drupal/entity/Tests/EntityFormDisplayTest.php b/core/modules/entity/lib/Drupal/entity/Tests/EntityFormDisplayTest.php
index a364b3f..5ee2a89 100644
--- a/core/modules/entity/lib/Drupal/entity/Tests/EntityFormDisplayTest.php
+++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityFormDisplayTest.php
@@ -14,7 +14,7 @@
  */
 class EntityFormDisplayTest extends DrupalUnitTestBase {
 
-  public static $modules = array('entity', 'field', 'entity_test');
+  public static $modules = array('entity', 'field', 'entity_test', 'user');
 
   public static function getInfo() {
     return array(
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/ConfigurableEntityReferenceItem.php b/core/modules/entity_reference/lib/Drupal/entity_reference/ConfigurableEntityReferenceItem.php
index 533610d..0f708c3 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/ConfigurableEntityReferenceItem.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/ConfigurableEntityReferenceItem.php
@@ -53,7 +53,7 @@ public function getPropertyDefinitions() {
       // Only add the revision ID property if the target entity type supports
       // revisions.
       $target_type_info = \Drupal::entityManager()->getDefinition($target_type);
-      if (!empty($target_type_info['entity_keys']['revision']) && !empty($target_type_info['revision_table'])) {
+      if ($target_type_info->hasKey('revision') && $target_type_info->getRevisionTable()) {
         static::$propertyDefinitions[$key]['revision_id'] = DataDefinition::create('integer')
           ->setLabel(t('Revision ID'))
           ->setConstraints(array('Range' => array('min' => 0)));
@@ -70,7 +70,7 @@ public static function schema(FieldInterface $field) {
     $target_type = $field->getSetting('target_type');
     $target_type_info = \Drupal::entityManager()->getDefinition($target_type);
 
-    if (is_subclass_of($target_type_info['class'], '\Drupal\Core\Entity\ContentEntityInterface')) {
+    if ($target_type_info->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) {
       $columns = array(
         'target_id' => array(
           'description' => 'The ID of the target entity.',
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Derivative/SelectionBase.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Derivative/SelectionBase.php
index a1d7991..3e41599 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Derivative/SelectionBase.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Derivative/SelectionBase.php
@@ -24,10 +24,10 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
       'taxonomy_term',
       'user'
     );
-    foreach (entity_get_info() as $entity_type => $info) {
+    foreach (\Drupal::entityManager()->getDefinitions() as $entity_type => $info) {
       if (!in_array($entity_type, $supported_entities)) {
         $this->derivatives[$entity_type] = $base_plugin_definition;
-        $this->derivatives[$entity_type]['label'] = t('@entity_type selection', array('@entity_type' => $info['label']));
+        $this->derivatives[$entity_type]['label'] = t('@entity_type selection', array('@entity_type' => $info->getLabel()));
       }
     }
     return parent::getDerivativeDefinitions($base_plugin_definition);
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldWidget/AutocompleteWidgetBase.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldWidget/AutocompleteWidgetBase.php
index 301ee9a..4621c5c 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldWidget/AutocompleteWidgetBase.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldWidget/AutocompleteWidgetBase.php
@@ -169,8 +169,8 @@ protected function createNewEntity($label, $uid) {
     }
 
     $entity_info = $entity_manager->getDefinition($target_type);
-    $bundle_key = $entity_info['entity_keys']['bundle'];
-    $label_key = $entity_info['entity_keys']['label'];
+    $bundle_key = $entity_info->getKey('bundle');
+    $label_key = $entity_info->getKey('label');
 
     return $entity_manager->getStorageController($target_type)->create(array(
       $label_key => $label,
@@ -201,7 +201,7 @@ protected function getSelectionHandlerSetting($setting_name) {
   protected function isContentReferenced() {
     $target_type = $this->getFieldSetting('target_type');
     $target_type_info = \Drupal::entityManager()->getDefinition($target_type);
-    return is_subclass_of($target_type_info['class'], '\Drupal\Core\Entity\ContentEntityInterface');
+    return $target_type_info->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface');
   }
 
 }
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/entity_reference/selection/SelectionBase.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/entity_reference/selection/SelectionBase.php
index abecaa3..0986c17 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/entity_reference/selection/SelectionBase.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/entity_reference/selection/SelectionBase.php
@@ -67,7 +67,7 @@ public static function settingsForm(FieldDefinitionInterface $field_definition)
       'auto_create' => FALSE,
     );
 
-    if (!empty($entity_info['entity_keys']['bundle'])) {
+    if ($entity_info->hasKey('bundle')) {
       $bundle_options = array();
       foreach ($bundles as $bundle_name => $bundle_info) {
         $bundle_options[$bundle_name] = $bundle_info['label'];
@@ -101,10 +101,10 @@ public static function settingsForm(FieldDefinitionInterface $field_definition)
     }
 
     $target_type_info = \Drupal::entityManager()->getDefinition($target_type);
-    if (is_subclass_of($target_type_info['class'], '\Drupal\Core\Entity\ContentEntityInterface')) {
+    if ($target_type_info->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) {
       // @todo Use Entity::getPropertyDefinitions() when all entity types are
       // converted to the new Field API.
-      $fields = drupal_map_assoc(drupal_schema_fields_sql($entity_info['base_table']));
+      $fields = drupal_map_assoc(drupal_schema_fields_sql($entity_info->getBaseTable()));
       foreach (field_info_instances($target_type) as $bundle_instances) {
         foreach ($bundle_instances as $instance_name => $instance) {
           foreach ($instance->getField()->getColumns() as $column_name => $column_info) {
@@ -197,10 +197,10 @@ public function validateReferenceableEntities(array $ids) {
     $result = array();
     if ($ids) {
       $target_type = $this->fieldDefinition->getSetting('target_type');
-      $entity_info = entity_get_info($target_type);
+      $entity_info = \Drupal::entityManager()->getDefinition($target_type);
       $query = $this->buildEntityQuery();
       $result = $query
-        ->condition($entity_info['entity_keys']['id'], $ids, 'IN')
+        ->condition($entity_info->getKey('id'), $ids, 'IN')
         ->execute();
     }
 
@@ -258,15 +258,15 @@ public function validateAutocompleteInput($input, &$element, &$form_state, $form
   public function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
     $target_type = $this->fieldDefinition->getSetting('target_type');
     $handler_settings = $this->fieldDefinition->getSetting('handler_settings');
-    $entity_info = entity_get_info($target_type);
+    $entity_info = \Drupal::entityManager()->getDefinition($target_type);
 
     $query = \Drupal::entityQuery($target_type);
     if (!empty($handler_settings['target_bundles'])) {
-      $query->condition($entity_info['entity_keys']['bundle'], $handler_settings['target_bundles'], 'IN');
+      $query->condition($entity_info->getKey('bundle'), $handler_settings['target_bundles'], 'IN');
     }
 
-    if (isset($match) && isset($entity_info['entity_keys']['label'])) {
-      $query->condition($entity_info['entity_keys']['label'], $match, $match_operator);
+    if (isset($match) && $label_key = $entity_info->getKey('label')) {
+      $query->condition($label_key, $match, $match_operator);
     }
 
     // Add entity-access tag.
diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc
index 50b3cb2..75ee6a2 100644
--- a/core/modules/field/field.attach.inc
+++ b/core/modules/field/field.attach.inc
@@ -36,9 +36,11 @@
  *
  * The Field API reads the bundle name for a given entity from a particular
  * property of the entity object, and hook_entity_info_alter() defines which
- * property to use. For instance, node_entity_info_alter() specifies:
+ * property to use. For instance, \Drupal\node\Entity\Node specifies:
  * @code
- *   $info['entity_keys']['bundle'] = 'type'
+ *   entity_keys = {
+ *     "bundle" = "type"
+ *   }
  * @endcode
  * This indicates that for a particular node object, the bundle name can be
  * found in $node->type. This property can be omitted if the entity type only
diff --git a/core/modules/field/field.install b/core/modules/field/field.install
index 83fc267..13aa45c 100644
--- a/core/modules/field/field.install
+++ b/core/modules/field/field.install
@@ -462,7 +462,7 @@ function field_update_8005() {
     'mode' => 'register',
     'content' => array(),
   );
-  foreach (config_get_storage_names_with_prefix('field.instance') as $config_id) {
+  foreach (config_get_storage_names_with_prefix('field.instance.') as $config_id) {
     $instance_config = \Drupal::config($config_id);
     if ($instance_config->get('entity_type') == 'user' && $instance_config->get('settings.user_register_form')) {
       list(, , $field_id) = explode('.', $instance_config->get('id'));
@@ -485,7 +485,7 @@ function field_update_8005() {
  */
 function field_update_8006(&$sandbox) {
   // Get field definitions from config, and deleted fields from state system.
-  $config_names = config_get_storage_names_with_prefix('field.field');
+  $config_names = config_get_storage_names_with_prefix('field.field.');
   $deleted_fields = \Drupal::state()->get('field.field.deleted') ?: array();
   // Ditch UUID keys, we will iterate through deleted fields using a numeric
   // index.
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 8b80c56..b2e8c76 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -488,9 +488,9 @@ function field_extract_bundle($entity_type, $bundle) {
     return $bundle;
   }
 
-  $info = entity_get_info($entity_type);
-  if (is_object($bundle) && isset($info['bundle_keys']['bundle']) && isset($bundle->{$info['bundle_keys']['bundle']})) {
-    return $bundle->{$info['bundle_keys']['bundle']};
+  $info = \Drupal::entityManager()->getDefinition($entity_type);
+  if (is_object($bundle) && ($bundle_key = $info->getBundleKey('bundle')) && isset($bundle->{$bundle_key})) {
+    return $bundle->{$bundle_key};
   }
 }
 
@@ -700,15 +700,15 @@ function theme_field($variables) {
  */
 function _field_create_entity_from_ids($ids) {
   $id_properties = array();
-  $info = entity_get_info($ids->entity_type);
-  if (isset($info['entity_keys']['id'])) {
-    $id_properties[$info['entity_keys']['id']] = $ids->entity_id;
+  $info = \Drupal::entityManager()->getDefinition($ids->entity_type);
+  if ($id_key = $info->getKey('id')) {
+    $id_properties[$id_key] = $ids->entity_id;
   }
-  if (!empty($info['entity_keys']['revision']) && isset($ids->revision_id)) {
-    $id_properties[$info['entity_keys']['revision']] = $ids->revision_id;
+  if (isset($ids->revision_id) && $revision_key = $info->getKey('revision')) {
+    $id_properties[$revision_key] = $ids->revision_id;
   }
-  if (!empty($info['entity_keys']['bundle']) && isset($ids->bundle)) {
-    $id_properties[$info['entity_keys']['bundle']] = $ids->bundle;
+  if (isset($ids->bundle) && $bundle_key = $info->getKey('bundle')) {
+    $id_properties[$bundle_key] = $ids->bundle;
   }
   return entity_create($ids->entity_type, $id_properties);
 }
diff --git a/core/modules/field/field.purge.inc b/core/modules/field/field.purge.inc
index 7e1e90a..95af3a7 100644
--- a/core/modules/field/field.purge.inc
+++ b/core/modules/field/field.purge.inc
@@ -74,7 +74,7 @@ function field_purge_batch($batch_size) {
   // because that function does not return deleted instances.
   $instances = entity_load_multiple_by_properties('field_instance', array('deleted' => TRUE, 'include_deleted' => TRUE));
   $factory = \Drupal::service('entity.query');
-  $info = entity_get_info();
+  $info = \Drupal::entityManager()->getDefinitions();
   foreach ($instances as $instance) {
     $entity_type = $instance->entity_type;
 
@@ -100,8 +100,8 @@ function field_purge_batch($batch_size) {
       ->condition('id:' . $instance->getField()->uuid() . '.deleted', 1)
       ->range(0, $batch_size);
     // If there's no bundle key, all results will have the same bundle.
-    if (!empty($info[$entity_type]['entity_keys']['bundle'])) {
-      $query->condition($info[$entity_type]['entity_keys']['bundle'], $ids->bundle);
+    if ($bundle_key = $info[$entity_type]->getKey('bundle')) {
+      $query->condition($bundle_key, $ids->bundle);
     }
     $results = $query->execute();
     if ($results) {
diff --git a/core/modules/field/field.views.inc b/core/modules/field/field.views.inc
index 7def00e..e9e65c4 100644
--- a/core/modules/field/field.views.inc
+++ b/core/modules/field/field.views.inc
@@ -132,14 +132,13 @@ function field_views_field_default_views_data(FieldInterface $field) {
   $entity_manager = \Drupal::entityManager();
   $entity_type = $field->entity_type;
   $entity_info = $entity_manager->getDefinition($entity_type);
-  if (!isset($entity_info['base_table'])) {
+  if (!$entity_table = $entity_info->getBaseTable()) {
     return $data;
   }
-  $entity_table = $entity_info['base_table'];
   $entity_tables = array($entity_table => $entity_type);
-  $supports_revisions = !empty($entity_info['entity_keys']['revision']) && !empty($entity_info['revision_table']);
+  $entity_revision_table = $entity_info->getRevisionTable();
+  $supports_revisions = $entity_info->hasKey('revision') && $entity_revision_table;
   if ($supports_revisions) {
-    $entity_revision_table = $entity_info['revision_table'];
     $entity_tables[$entity_revision_table] = $entity_type;
   }
 
@@ -160,7 +159,7 @@ function field_views_field_default_views_data(FieldInterface $field) {
   // Build the relationships between the field table and the entity tables.
   $table_alias = $field_tables[EntityStorageControllerInterface::FIELD_LOAD_CURRENT]['alias'];
   $data[$table_alias]['table']['join'][$entity_table] = array(
-    'left_field' => $entity_info['entity_keys']['id'],
+    'left_field' => $entity_info->getKey('id'),
     'field' => 'entity_id',
     'extra' => array(
       array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
@@ -169,7 +168,7 @@ function field_views_field_default_views_data(FieldInterface $field) {
   if ($supports_revisions) {
     $table_alias = $field_tables[EntityStorageControllerInterface::FIELD_LOAD_REVISION]['alias'];
     $data[$table_alias]['table']['join'][$entity_revision_table] = array(
-      'left_field' => $entity_info['entity_keys']['revision'],
+      'left_field' => $entity_info->getKey('revision'),
       'field' => 'revision_id',
       'extra' => array(
         array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE),
@@ -177,7 +176,7 @@ function field_views_field_default_views_data(FieldInterface $field) {
     );
   }
 
-  $group_name = $entity_info['label'];
+  $group_name = $entity_info->getLabel();
   // Get the list of bundles the field appears in.
   $bundles_names = $field->getBundles();
   // Build the list of additional fields to add to queries.
diff --git a/core/modules/field/lib/Drupal/field/Entity/Field.php b/core/modules/field/lib/Drupal/field/Entity/Field.php
index e1f89e2..098d83f 100644
--- a/core/modules/field/lib/Drupal/field/Entity/Field.php
+++ b/core/modules/field/lib/Drupal/field/Entity/Field.php
@@ -326,7 +326,7 @@ protected function preSaveNew(EntityStorageControllerInterface $storage_controll
     // collisions with existing entity properties, but some is better than
     // none.
     foreach ($entity_manager->getDefinitions() as $type => $info) {
-      if (in_array($this->name, $info['entity_keys'])) {
+      if (in_array($this->name, $info->getKeys())) {
         throw new FieldException(format_string('Attempt to create field %name which is reserved by entity type %type.', array('%name' => $this->name, '%type' => $type)));
       }
     }
@@ -654,7 +654,7 @@ public function hasData() {
       $factory = \Drupal::service('entity.query');
       // Entity Query throws an exception if there is no base table.
       $entity_info = \Drupal::entityManager()->getDefinition($this->entity_type);
-      if (!isset($entity_info['base_table'])) {
+      if (!$entity_info->getBaseTable()) {
         return FALSE;
       }
       $query = $factory->get($this->entity_type);
diff --git a/core/modules/field/lib/Drupal/field/FieldInfo.php b/core/modules/field/lib/Drupal/field/FieldInfo.php
index 482b040..4df7ab8 100644
--- a/core/modules/field/lib/Drupal/field/FieldInfo.php
+++ b/core/modules/field/lib/Drupal/field/FieldInfo.php
@@ -437,7 +437,7 @@ public function getBundleInstances($entity_type, $bundle) {
     $fields = array();
 
     // Do not return anything for unknown entity types.
-    if (entity_get_info($entity_type) && !empty($field_map[$entity_type])) {
+    if (\Drupal::entityManager()->getDefinition($entity_type) && !empty($field_map[$entity_type])) {
 
       // Collect names of fields and instances involved in the bundle, using the
       // field map. The field map is already filtered to non-deleted fields and
diff --git a/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php b/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php
index 3b3e397..ef25a51 100644
--- a/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php
+++ b/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Config\Config;
 use Drupal\Core\Config\Entity\ConfigStorageController;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\Query\QueryFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Config\ConfigFactory;
@@ -52,10 +53,8 @@ class FieldInstanceStorageController extends ConfigStorageController {
   /**
    * Constructs a FieldInstanceStorageController object.
    *
-   * @param string $entity_type
-   *   The entity type for which the instance is created.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Config\ConfigFactory $config_factory
    *   The config factory service.
    * @param \Drupal\Core\Config\StorageInterface $config_storage
@@ -71,8 +70,8 @@ class FieldInstanceStorageController extends ConfigStorageController {
    * @param \Drupal\Core\KeyValueStore\StateInterface $state
    *   The state key value store.
    */
-  public function __construct($entity_type, array $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, QueryFactory $entity_query_factory, UuidInterface $uuid_service, EntityManagerInterface $entity_manager, ModuleHandler $module_handler, StateInterface $state) {
-    parent::__construct($entity_type, $entity_info, $config_factory, $config_storage, $entity_query_factory, $uuid_service);
+  public function __construct(EntityTypeInterface $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, QueryFactory $entity_query_factory, UuidInterface $uuid_service, EntityManagerInterface $entity_manager, ModuleHandler $module_handler, StateInterface $state) {
+    parent::__construct($entity_info, $config_factory, $config_storage, $entity_query_factory, $uuid_service);
     $this->entityManager = $entity_manager;
     $this->moduleHandler = $module_handler;
     $this->state = $state;
@@ -81,9 +80,8 @@ public function __construct($entity_type, array $entity_info, ConfigFactory $con
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
       $container->get('config.factory'),
       $container->get('config.storage'),
diff --git a/core/modules/field/lib/Drupal/field/FieldStorageController.php b/core/modules/field/lib/Drupal/field/FieldStorageController.php
index eac89c2..a1f9716 100644
--- a/core/modules/field/lib/Drupal/field/FieldStorageController.php
+++ b/core/modules/field/lib/Drupal/field/FieldStorageController.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Config\Config;
 use Drupal\Core\Config\Entity\ConfigStorageController;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\Query\QueryFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Config\ConfigFactory;
@@ -47,10 +48,8 @@ class FieldStorageController extends ConfigStorageController {
   /**
    * Constructs a FieldStorageController object.
    *
-   * @param string $entity_type
-   *   The entity type for which the instance is created.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Config\ConfigFactory $config_factory
    *   The config factory service.
    * @param \Drupal\Core\Config\StorageInterface $config_storage
@@ -66,8 +65,8 @@ class FieldStorageController extends ConfigStorageController {
    * @param \Drupal\Core\KeyValueStore\StateInterface $state
    *   The state key value store.
    */
-  public function __construct($entity_type, array $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, QueryFactory $entity_query_factory, UuidInterface $uuid_service, EntityManagerInterface $entity_manager, ModuleHandler $module_handler, StateInterface $state) {
-    parent::__construct($entity_type, $entity_info, $config_factory, $config_storage, $entity_query_factory, $uuid_service);
+  public function __construct(EntityTypeInterface $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, QueryFactory $entity_query_factory, UuidInterface $uuid_service, EntityManagerInterface $entity_manager, ModuleHandler $module_handler, StateInterface $state) {
+    parent::__construct($entity_info, $config_factory, $config_storage, $entity_query_factory, $uuid_service);
     $this->entityManager = $entity_manager;
     $this->moduleHandler = $module_handler;
     $this->state = $state;
@@ -76,9 +75,8 @@ public function __construct($entity_type, array $entity_info, ConfigFactory $con
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
       $container->get('config.factory'),
       $container->get('config.storage'),
diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php
index b1b9346..03fc549 100644
--- a/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php
@@ -70,10 +70,10 @@ function testFieldInfo() {
     $instance = entity_create('field_instance', $instance_definition);
     $instance->save();
 
-    $info = entity_get_info('entity_test');
+    $info = \Drupal::entityManager()->getDefinition('entity_test');
     $instances = field_info_instances('entity_test', $instance->bundle);
     $this->assertEqual(count($instances), 1, format_string('One instance shows up in info when attached to a bundle on a @label.', array(
-      '@label' => $info['label']
+      '@label' => $info->getLabel(),
     )));
     $this->assertTrue($instance_definition < $instances[$instance->getName()], 'Instance appears in info correctly');
 
diff --git a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php
index b30625b..7509fbd 100644
--- a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php
@@ -118,8 +118,8 @@ function setUp() {
   function testTranslatableFieldSaveLoad() {
     // Enable field translations for nodes.
     field_test_entity_info_translatable('node', TRUE);
-    $entity_info = entity_get_info('node');
-    $this->assertTrue(count($entity_info['translatable']), 'Nodes are translatable.');
+    $entity_info = \Drupal::entityManager()->getDefinition('node');
+    $this->assertTrue($entity_info->isTranslatable(), 'Nodes are translatable.');
 
     // Prepare the field translations.
     $entity_type = 'entity_test';
diff --git a/core/modules/field/tests/modules/field_test/field_test.entity.inc b/core/modules/field/tests/modules/field_test/field_test.entity.inc
index a642b85..af6482d 100644
--- a/core/modules/field/tests/modules/field_test/field_test.entity.inc
+++ b/core/modules/field/tests/modules/field_test/field_test.entity.inc
@@ -9,6 +9,7 @@
  * Implements hook_entity_info().
  */
 function field_test_entity_info() {
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
   // If requested, clear the field cache while this is being called. See
   // Drupal\field\Tests\FieldInfoTest::testFieldInfoCache().
   if (\Drupal::state()->get('field_test.clear_info_cache_in_hook_entity_info')) {
@@ -20,8 +21,9 @@ function field_test_entity_info() {
  * Implements hook_entity_info_alter().
  */
 function field_test_entity_info_alter(&$entity_info) {
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
   foreach (field_test_entity_info_translatable() as $entity_type => $translatable) {
-    $entity_info[$entity_type]['translatable'] = $translatable;
+    $entity_info[$entity_type]->set('translatable', $translatable);
   }
 }
 
diff --git a/core/modules/field_ui/field_ui.local_tasks.yml b/core/modules/field_ui/field_ui.local_tasks.yml
index 1330c2d..d724933 100644
--- a/core/modules/field_ui/field_ui.local_tasks.yml
+++ b/core/modules/field_ui/field_ui.local_tasks.yml
@@ -1,7 +1,7 @@
 field_ui.list:
   title: Entities
   route_name: field_ui.list
-  tab_root_id: field_ui.list
+  base_route: field_ui.list
 field_ui.fields:
   class: \Drupal\Core\Menu\LocalTaskDefault
   derivative: \Drupal\field_ui\Plugin\Derivative\FieldUiLocalTask
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index bc50a76..cdcc9be 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -71,19 +71,19 @@ function field_ui_menu() {
 function field_ui_permission() {
   $permissions = array();
 
-  foreach (entity_get_info() as $entity_type => $entity_info) {
-    if ($entity_info['fieldable']) {
+  foreach (\Drupal::entityManager()->getDefinitions() as $entity_type => $entity_info) {
+    if ($entity_info->isFieldable()) {
       // Create a permission for each fieldable entity to manage
       // the fields and the display.
       $permissions['administer ' . $entity_type . ' fields'] = array(
-        'title' => t('%entity_label: Administer fields', array('%entity_label' => $entity_info['label'])),
+        'title' => t('%entity_label: Administer fields', array('%entity_label' => $entity_info->getLabel())),
         'restrict access' => TRUE,
       );
       $permissions['administer ' . $entity_type . ' form display'] = array(
-        'title' => t('%entity_label: Administer form display', array('%entity_label' => $entity_info['label']))
+        'title' => t('%entity_label: Administer form display', array('%entity_label' => $entity_info->getLabel()))
       );
       $permissions['administer ' . $entity_type . ' display'] = array(
-        'title' => t('%entity_label: Administer display', array('%entity_label' => $entity_info['label']))
+        'title' => t('%entity_label: Administer display', array('%entity_label' => $entity_info->getLabel()))
       );
     }
   }
@@ -118,8 +118,9 @@ function field_ui_element_info() {
  * Implements hook_entity_info().
  */
 function field_ui_entity_info(&$entity_info) {
-  $entity_info['field_instance']['controllers']['form']['delete'] = 'Drupal\field_ui\Form\FieldDeleteForm';
-  $entity_info['field_entity']['controllers']['list'] = 'Drupal\field_ui\FieldListController';
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
+  $entity_info['field_instance']->setForm('delete', 'Drupal\field_ui\Form\FieldDeleteForm');
+  $entity_info['field_entity']->setList('Drupal\field_ui\FieldListController');
 }
 
 /**
@@ -167,8 +168,7 @@ function field_ui_entity_operation_alter(array &$operations, EntityInterface $en
   $info = $entity->entityInfo();
   // Add manage fields and display links if this entity type is the bundle
   // of another.
-  if (!empty($info['bundle_of'])) {
-    $bundle_of = $info['bundle_of'];
+  if ($bundle_of = $info->getBundleOf()) {
     $uri = $entity->uri();
     if (user_access('administer '. $bundle_of . ' fields')) {
       $operations['manage-fields'] = array(
diff --git a/core/modules/field_ui/field_ui.services.yml b/core/modules/field_ui/field_ui.services.yml
index 65111cc..4f5ef7a 100644
--- a/core/modules/field_ui/field_ui.services.yml
+++ b/core/modules/field_ui/field_ui.services.yml
@@ -8,9 +8,9 @@ services:
     class: Drupal\field_ui\Access\ViewModeAccessCheck
     arguments: ['@entity.manager']
     tags:
-     - { name: access_check }
+     - { name: access_check, applies_to: _field_ui_view_mode_access }
   access_check.field_ui.form_mode:
     class: Drupal\field_ui\Access\FormModeAccessCheck
     arguments: ['@entity.manager']
     tags:
-     - { name: access_check }
+     - { name: access_check, applies_to: _field_ui_form_mode_access }
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Access/FormModeAccessCheck.php b/core/modules/field_ui/lib/Drupal/field_ui/Access/FormModeAccessCheck.php
index 10552da..ece0c63 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/Access/FormModeAccessCheck.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/Access/FormModeAccessCheck.php
@@ -7,8 +7,8 @@
 
 namespace Drupal\field_ui\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -16,7 +16,7 @@
 /**
  * Allows access to routes to be controlled by an '_access' boolean parameter.
  */
-class FormModeAccessCheck implements StaticAccessCheckInterface {
+class FormModeAccessCheck implements AccessInterface {
 
   /**
    * The entity manager.
@@ -38,20 +38,13 @@ public function __construct(EntityManagerInterface $entity_manager) {
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    return array('_field_ui_form_mode_access');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     if ($entity_type = $route->getDefault('entity_type')) {
       $form_mode = $request->attributes->get('form_mode_name');
 
       if (!($bundle = $request->attributes->get('bundle'))) {
         $entity_info = $this->entityManager->getDefinition($entity_type);
-        $bundle = $request->attributes->get('_raw_variables')->get($entity_info['bundle_entity_type']);
+        $bundle = $request->attributes->get('_raw_variables')->get($entity_info->getBundleEntityType());
       }
 
       $visibility = FALSE;
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Access/ViewModeAccessCheck.php b/core/modules/field_ui/lib/Drupal/field_ui/Access/ViewModeAccessCheck.php
index e5fb8d5..393fa7b 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/Access/ViewModeAccessCheck.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/Access/ViewModeAccessCheck.php
@@ -7,8 +7,8 @@
 
 namespace Drupal\field_ui\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -16,7 +16,7 @@
 /**
  * Allows access to routes to be controlled by an '_access' boolean parameter.
  */
-class ViewModeAccessCheck implements StaticAccessCheckInterface {
+class ViewModeAccessCheck implements AccessInterface {
 
   /**
    * The entity manager.
@@ -38,20 +38,13 @@ public function __construct(EntityManagerInterface $entity_manager) {
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    return array('_field_ui_view_mode_access');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     if ($entity_type = $route->getDefault('entity_type')) {
       $view_mode = $request->attributes->get('view_mode_name');
 
       if (!($bundle = $request->attributes->get('bundle'))) {
         $entity_info = $this->entityManager->getDefinition($entity_type);
-        $bundle = $request->attributes->get('_raw_variables')->get($entity_info['bundle_entity_type']);
+        $bundle = $request->attributes->get('_raw_variables')->get($entity_info->getBundleEntityType());
       }
 
       $visibility = FALSE;
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverviewBase.php b/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverviewBase.php
index 0858119..3a9b442 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverviewBase.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverviewBase.php
@@ -741,8 +741,8 @@ protected function getDisplays() {
     $load_ids = array();
     $display_entity_type = $this->getDisplayType();
     $entity_info = $this->entityManager->getDefinition($display_entity_type);
-    $config_prefix = $entity_info['config_prefix'];
-    $ids = config_get_storage_names_with_prefix($config_prefix . '.' . $this->entity_type . '.' . $this->bundle);
+    $config_prefix = $entity_info->getConfigPrefix();
+    $ids = config_get_storage_names_with_prefix($config_prefix . '.' . $this->entity_type . '.' . $this->bundle . '.');
     foreach ($ids as $id) {
       $config_id = str_replace($config_prefix . '.', '', $id);
       list(,, $display_mode) = explode('.', $config_id);
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/FieldListController.php b/core/modules/field_ui/lib/Drupal/field_ui/FieldListController.php
index 16ecfe9..76efe54 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/FieldListController.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/FieldListController.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\FieldTypePluginManager;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -50,21 +51,17 @@ class FieldListController extends ConfigEntityListController {
   /**
    * Constructs a new EntityListController object.
    *
-   * @param string $entity_type
-   *   The type of entity to be listed.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
-   * @param \Drupal\field\FieldInfo $field_info
-   *   The field info service.
    * @param \Drupal\Core\Field\FieldTypePluginManager $field_type_manager
    *   The 'field type' plugin manager.
    */
-  public function __construct($entity_type, array $entity_info, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, FieldTypePluginManager $field_type_manager) {
-    parent::__construct($entity_type, $entity_info, $entity_manager->getStorageController($entity_type), $module_handler);
+  public function __construct(EntityTypeInterface $entity_info, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, FieldTypePluginManager $field_type_manager) {
+    parent::__construct($entity_info, $entity_manager->getStorageController($entity_info->id()), $module_handler);
 
     $this->entityManager = $entity_manager;
     $this->bundles = entity_get_bundles();
@@ -75,9 +72,8 @@ public function __construct($entity_type, array $entity_info, EntityManagerInter
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
       $container->get('entity.manager'),
       $container->get('module_handler'),
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldInstanceEditForm.php b/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldInstanceEditForm.php
index 4a81159..3d02cd5 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldInstanceEditForm.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/Form/FieldInstanceEditForm.php
@@ -212,7 +212,7 @@ public function delete(array &$form, array &$form_state) {
     $form_state['redirect_route'] = array(
       'route_name' => 'field_ui.delete_' . $this->instance->entity_type,
       'route_parameters' => array(
-        $entity_info['bundle_entity_type'] => $this->instance->bundle,
+        $entity_info->getBundleEntityType() => $this->instance->bundle,
         'field_instance' => $this->instance->id(),
       ),
       'options' => array(
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/OverviewBase.php b/core/modules/field_ui/lib/Drupal/field_ui/OverviewBase.php
index 272d807..61674d4 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/OverviewBase.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/OverviewBase.php
@@ -77,7 +77,7 @@ public function buildForm(array $form, array &$form_state, $entity_type = NULL,
     if (!isset($form_state['bundle'])) {
       if (!$bundle) {
         $entity_info = $this->entityManager->getDefinition($entity_type);
-        $bundle = $this->getRequest()->attributes->get('_raw_variables')->get($entity_info['bundle_entity_type']);
+        $bundle = $this->getRequest()->attributes->get('_raw_variables')->get($entity_info->getBundleEntityType());
       }
       $form_state['bundle'] = $bundle;
     }
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Plugin/Derivative/FieldUiLocalTask.php b/core/modules/field_ui/lib/Drupal/field_ui/Plugin/Derivative/FieldUiLocalTask.php
index 9a7c208..602acc2 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/Plugin/Derivative/FieldUiLocalTask.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/Plugin/Derivative/FieldUiLocalTask.php
@@ -8,7 +8,7 @@
 namespace Drupal\field_ui\Plugin\Derivative;
 
 use Drupal\Core\Entity\EntityManagerInterface;
-use Drupal\Core\Menu\LocalTaskDerivativeBase;
+use Drupal\Component\Plugin\Derivative\DerivativeBase;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeInterface;
 use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
@@ -17,7 +17,7 @@
 /**
  * Provides local task definitions for all entity bundles.
  */
-class FieldUiLocalTask extends LocalTaskDerivativeBase implements ContainerDerivativeInterface {
+class FieldUiLocalTask extends DerivativeBase implements ContainerDerivativeInterface {
 
   /**
    * The route provider.
@@ -74,12 +74,12 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
     $this->derivatives = array();
 
     foreach ($this->entityManager->getDefinitions() as $entity_type => $entity_info) {
-      if ($entity_info['fieldable'] && isset($entity_info['links']['admin-form'])) {
+      if ($entity_info->isFieldable() && $entity_info->hasLinkTemplate('admin-form')) {
         $this->derivatives["overview_$entity_type"] = array(
           'route_name' => "field_ui.overview_$entity_type",
           'weight' => 1,
           'title' => $this->t('Manage fields'),
-          'tab_root_id' => "field_ui.fields:overview_$entity_type",
+          'base_route' => "field_ui.overview_$entity_type",
         );
 
         // 'Manage form display' tab.
@@ -87,7 +87,7 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
           'route_name' => "field_ui.form_display_overview_$entity_type",
           'weight' => 2,
           'title' => $this->t('Manage form display'),
-          'tab_root_id' => "field_ui.fields:overview_$entity_type",
+          'base_route' => "field_ui.overview_$entity_type",
         );
 
         // 'Manage display' tab.
@@ -95,21 +95,21 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
           'route_name' => "field_ui.display_overview_$entity_type",
           'weight' => 3,
           'title' => $this->t('Manage display'),
-          'tab_root_id' => "field_ui.fields:overview_$entity_type",
+          'base_route' => "field_ui.overview_$entity_type",
         );
 
         // Field instance edit tab.
         $this->derivatives["instance_edit_$entity_type"] = array(
           'route_name' => "field_ui.instance_edit_$entity_type",
           'title' => $this->t('Edit'),
-          'tab_root_id' => "field_ui.fields:instance_edit_$entity_type",
+          'base_route' => "field_ui.instance_edit_$entity_type",
         );
 
         // Field settings tab.
         $this->derivatives["field_edit_$entity_type"] = array(
           'route_name' => "field_ui.field_edit_$entity_type",
           'title' => $this->t('Field settings'),
-          'tab_root_id' => "field_ui.fields:instance_edit_$entity_type",
+          'base_route' => "field_ui.instance_edit_$entity_type",
         );
 
         // View and form modes secondary tabs.
@@ -122,14 +122,12 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
         $this->derivatives['field_form_display_default_' . $entity_type] = array(
           'title' => 'Default',
           'route_name' => "field_ui.form_display_overview_$entity_type",
-          'tab_root_id' => "field_ui.fields:overview_$entity_type",
-          'tab_parent_id' => "field_ui.fields:form_display_overview_$entity_type",
+          'parent_id' => "field_ui.fields:form_display_overview_$entity_type",
         );
         $this->derivatives['field_display_default_' . $entity_type] = array(
           'title' => 'Default',
           'route_name' => "field_ui.display_overview_$entity_type",
-          'tab_root_id' => "field_ui.fields:overview_$entity_type",
-          'tab_parent_id' => "field_ui.fields:display_overview_$entity_type",
+          'parent_id' => "field_ui.fields:display_overview_$entity_type",
         );
 
         // One local task for each form mode.
@@ -141,8 +139,7 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
             'route_parameters' => array(
               'form_mode_name' => $form_mode,
             ),
-            'tab_root_id' => "field_ui.fields:overview_$entity_type",
-            'tab_parent_id' => "field_ui.fields:form_display_overview_$entity_type",
+            'parent_id' => "field_ui.fields:form_display_overview_$entity_type",
             'weight' => $weight++,
           );
         }
@@ -156,8 +153,7 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
             'route_parameters' => array(
               'view_mode_name' => $view_mode,
             ),
-            'tab_root_id' => "field_ui.fields:overview_$entity_type",
-            'tab_parent_id' => "field_ui.fields:display_overview_$entity_type",
+            'parent_id' => "field_ui.fields:display_overview_$entity_type",
             'weight' => $weight++,
           );
         }
@@ -172,28 +168,27 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
   }
 
   /**
-   * Alters the tab_root_id definition for field_ui local tasks.
+   * Alters the base_route definition for field_ui local tasks.
    *
    * @param array $local_tasks
    *   An array of local tasks plugin definitions, keyed by plugin ID.
    */
   public function alterLocalTasks(&$local_tasks) {
     foreach ($this->entityManager->getDefinitions() as $entity_type => $entity_info) {
-      if ($entity_info['fieldable'] && isset($entity_info['links']['admin-form'])) {
-        if ($parent_task = $this->getPluginIdFromRoute($entity_info['links']['admin-form'], $local_tasks)) {
-          $local_tasks["field_ui.fields:overview_$entity_type"]['tab_root_id'] = $parent_task;
-          $local_tasks["field_ui.fields:form_display_overview_$entity_type"]['tab_root_id'] = $parent_task;
-          $local_tasks["field_ui.fields:display_overview_$entity_type"]['tab_root_id'] = $parent_task;
-          $local_tasks["field_ui.fields:field_form_display_default_$entity_type"]['tab_root_id'] = $parent_task;
-          $local_tasks["field_ui.fields:field_display_default_$entity_type"]['tab_root_id'] = $parent_task;
-
-          foreach (entity_get_form_modes($entity_type) as $form_mode => $form_mode_info) {
-            $local_tasks['field_ui.fields:field_form_display_' . $form_mode . '_' . $entity_type]['tab_root_id'] = $parent_task;
-          }
-
-          foreach (entity_get_view_modes($entity_type) as $view_mode => $form_mode_info) {
-            $local_tasks['field_ui.fields:field_display_' . $view_mode . '_' . $entity_type]['tab_root_id'] = $parent_task;
-          }
+      if ($entity_info->isFieldable() && $entity_info->hasLinkTemplate('admin-form')) {
+        $admin_form = $entity_info->getLinkTemplate('admin-form');
+        $local_tasks["field_ui.fields:overview_$entity_type"]['base_route'] = $admin_form;
+        $local_tasks["field_ui.fields:form_display_overview_$entity_type"]['base_route'] = $admin_form;
+        $local_tasks["field_ui.fields:display_overview_$entity_type"]['base_route'] = $admin_form;
+        $local_tasks["field_ui.fields:field_form_display_default_$entity_type"]['base_route'] = $admin_form;
+        $local_tasks["field_ui.fields:field_display_default_$entity_type"]['base_route'] = $admin_form;
+
+        foreach (entity_get_form_modes($entity_type) as $form_mode => $form_mode_info) {
+          $local_tasks['field_ui.fields:field_form_display_' . $form_mode . '_' . $entity_type]['base_route'] = $admin_form;
+        }
+
+        foreach (entity_get_view_modes($entity_type) as $view_mode => $form_mode_info) {
+          $local_tasks['field_ui.fields:field_display_' . $view_mode . '_' . $entity_type]['base_route'] = $admin_form;
         }
       }
     }
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php b/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php
index 1a20aee..b78a88f 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php
@@ -41,9 +41,9 @@ public function __construct(EntityManagerInterface $manager) {
   protected function alterRoutes(RouteCollection $collection, $provider) {
     foreach ($this->manager->getDefinitions() as $entity_type => $entity_info) {
       $defaults = array();
-      if ($entity_info['fieldable'] && isset($entity_info['links']['admin-form'])) {
+      if ($entity_info->isFieldable() && $entity_info->hasLinkTemplate('admin-form')) {
         // Try to get the route from the current collection.
-        if (!$entity_route = $collection->get($entity_info['links']['admin-form'])) {
+        if (!$entity_route = $collection->get($entity_info->getLinkTemplate('admin-form'))) {
           continue;
         }
         $path = $entity_route->getPath();
@@ -74,7 +74,7 @@ protected function alterRoutes(RouteCollection $collection, $provider) {
 
         // If the entity type has no bundles, use the entity type.
         $defaults['entity_type'] = $entity_type;
-        if (empty($entity_info['entity_keys']['bundle'])) {
+        if (!$entity_info->hasKey('bundle')) {
           $defaults['bundle'] = $entity_type;
         }
         $route = new Route(
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 4041886..10830d1 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -1852,11 +1852,11 @@ function file_get_file_references(File $file, $field = NULL, $age = EntityStorag
     $usage_list = \Drupal::service('file.usage')->listUsage($file);
     $file_usage_list = isset($usage_list['file']) ? $usage_list['file'] : array();
     foreach ($file_usage_list as $entity_type => $entity_ids) {
-      $entity_info = entity_get_info($entity_type);
+      $entity_info = \Drupal::entityManager()->getDefinition($entity_type);
       // The usage table contains usage of every revision. If we are looking
       // for every revision or the entity does not support revisions then
       // every usage is already a match.
-      $match_entity_type = $age == EntityStorageControllerInterface::FIELD_LOAD_REVISION || !isset($entity_info['entity_keys']['revision']);
+      $match_entity_type = $age == EntityStorageControllerInterface::FIELD_LOAD_REVISION || !$entity_info->hasKey('revision');
       $entities = entity_load_multiple($entity_type, array_keys($entity_ids));
       foreach ($entities as $entity) {
         $bundle = $entity->bundle();
diff --git a/core/modules/file/file.views.inc b/core/modules/file/file.views.inc
index 729ee4b..f78ec87 100644
--- a/core/modules/file/file.views.inc
+++ b/core/modules/file/file.views.inc
@@ -488,22 +488,22 @@ function file_field_views_data(FieldInterface $field) {
  */
 function file_field_views_data_views_data_alter(array &$data, FieldInterface $field) {
   $entity_type = $field->entity_type;
-  $entity_info = entity_get_info($entity_type);
+  $entity_info = \Drupal::entityManager()->getDefinition($entity_type);
   $field_name = $field->getName();
   $pseudo_field_name = 'reverse_' . $field_name . '_' . $entity_type;
 
   list($label) = field_views_field_label($entity_type, $field_name);
 
   $data['file_managed'][$pseudo_field_name]['relationship'] = array(
-    'title' => t('@entity using @field', array('@entity' => $entity_info['label'], '@field' => $label)),
-    'help' => t('Relate each @entity with a @field set to the file.', array('@entity' => $entity_info['label'], '@field' => $label)),
+    'title' => t('@entity using @field', array('@entity' => $entity_info->getLabel(), '@field' => $label)),
+    'help' => t('Relate each @entity with a @field set to the file.', array('@entity' => $entity_info->getLabel(), '@field' => $label)),
     'id' => 'entity_reverse',
     'field_name' => $field_name,
     'entity_type' => $entity_type,
     'field table' => FieldableDatabaseStorageController::_fieldTableName($field),
     'field field' => $field_name . '_target_id',
-    'base' => $entity_info['base_table'],
-    'base field' => $entity_info['entity_keys']['id'],
+    'base' => $entity_info->getBaseTable(),
+    'base field' => $entity_info->getKey('id'),
     'label' => t('!field_name', array('!field_name' => $field_name)),
     'join_extra' => array(
       0 => array(
diff --git a/core/modules/file/lib/Drupal/file/FileStorageController.php b/core/modules/file/lib/Drupal/file/FileStorageController.php
index 9dbe289..15cf036 100644
--- a/core/modules/file/lib/Drupal/file/FileStorageController.php
+++ b/core/modules/file/lib/Drupal/file/FileStorageController.php
@@ -18,7 +18,7 @@ class FileStorageController extends FieldableDatabaseStorageController implement
    * {@inheritdoc}
    */
   public function spaceUsed($uid = NULL, $status = FILE_STATUS_PERMANENT) {
-    $query = $this->database->select($this->entityInfo['base_table'], 'f')
+    $query = $this->database->select($this->entityInfo->getBaseTable(), 'f')
       ->condition('f.status', $status);
     $query->addExpression('SUM(f.filesize)', 'filesize');
     if (isset($uid)) {
@@ -33,7 +33,7 @@ public function spaceUsed($uid = NULL, $status = FILE_STATUS_PERMANENT) {
   public function retrieveTemporaryFiles() {
     // Use separate placeholders for the status to avoid a bug in some versions
     // of PHP. See http://drupal.org/node/352956.
-    return $this->database->query('SELECT fid FROM {' . $this->entityInfo['base_table'] . '} WHERE status <> :permanent AND timestamp < :timestamp', array(
+    return $this->database->query('SELECT fid FROM {' . $this->entityInfo->getBaseTable() . '} WHERE status <> :permanent AND timestamp < :timestamp', array(
       ':permanent' => FILE_STATUS_PERMANENT,
       ':timestamp' => REQUEST_TIME - DRUPAL_MAXIMUM_TEMP_FILE_AGE
     ));
diff --git a/core/modules/file/lib/Drupal/file/Tests/FileFieldDisplayTest.php b/core/modules/file/lib/Drupal/file/Tests/FileFieldDisplayTest.php
index f6c399d..219c083 100644
--- a/core/modules/file/lib/Drupal/file/Tests/FileFieldDisplayTest.php
+++ b/core/modules/file/lib/Drupal/file/Tests/FileFieldDisplayTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\file\Tests;
 
+use Drupal\Core\Field\FieldDefinitionInterface;
+
 /**
  * Tests that formatters are working properly.
  */
@@ -29,6 +31,7 @@ function testNodeDisplay() {
     $field_settings = array(
       'display_field' => '1',
       'display_default' => '1',
+      'cardinality' => FieldDefinitionInterface::CARDINALITY_UNLIMITED,
     );
     $instance_settings = array(
       'description_field' => '1',
@@ -80,5 +83,17 @@ function testNodeDisplay() {
     );
     $this->drupalPostForm('node/' . $nid . '/edit', $edit, t('Save and keep published'));
     $this->assertText($description);
+
+    // Test that fields appear as expected after during the preview.
+    // Add a second file.
+    $name = 'files[' . $field_name . '_1][]';
+    $edit[$name] = drupal_realpath($test_file->getFileUri());
+
+    // Uncheck the display checkboxes and go to the preview.
+    $edit[$field_name . '[0][display]'] = FALSE;
+    $edit[$field_name . '[1][display]'] = FALSE;
+    $this->drupalPostForm("node/$nid/edit", $edit, t('Preview'));
+    $this->assertRaw($field_name . '[0][display]', 'First file appears as expected.');
+    $this->assertRaw($field_name . '[1][display]', 'Second file appears as expected.');
   }
 }
diff --git a/core/modules/filter/config/filter.format.plain_text.yml b/core/modules/filter/config/filter.format.plain_text.yml
index 97296ec..37650f9 100644
--- a/core/modules/filter/config/filter.format.plain_text.yml
+++ b/core/modules/filter/config/filter.format.plain_text.yml
@@ -15,14 +15,14 @@ filters:
   # Escape all HTML.
   filter_html_escape:
     id: filter_html_escape
-    module: filter
+    provider: filter
     status: true
     weight: -10
     settings: {  }
   # Convert URLs into links.
   filter_url:
     id: filter_url
-    module: filter
+    provider: filter
     status: true
     weight: 0
     settings:
@@ -30,7 +30,7 @@ filters:
   # Convert linebreaks into paragraphs.
   filter_autop:
     id: filter_autop
-    module: filter
+    provider: filter
     status: true
     weight: 0
     settings: {  }
diff --git a/core/modules/filter/filter.local_tasks.yml b/core/modules/filter/filter.local_tasks.yml
index 56cbb87..166132b 100644
--- a/core/modules/filter/filter.local_tasks.yml
+++ b/core/modules/filter/filter.local_tasks.yml
@@ -1,10 +1,10 @@
 filter.format_edit_tab:
   route_name: filter.format_edit
   title: 'Configure'
-  tab_root_id: filter.format_edit_tab
+  base_route: filter.format_edit
   weight: -10
 
 filter.admin_overview:
   title: List
   route_name: filter.admin_overview
-  tab_root_id: filter.admin_overview
+  base_route: filter.admin_overview
diff --git a/core/modules/filter/filter.services.yml b/core/modules/filter/filter.services.yml
index 91bc91f..c355c0c 100644
--- a/core/modules/filter/filter.services.yml
+++ b/core/modules/filter/filter.services.yml
@@ -9,7 +9,7 @@ services:
   access_check.filter_disable:
     class: Drupal\filter\Access\FormatDisableCheck
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _filter_disable_format_access }
   plugin.manager.filter:
     class: Drupal\filter\FilterPluginManager
     parent: default_plugin_manager
diff --git a/core/modules/filter/lib/Drupal/filter/Access/FormatDisableCheck.php b/core/modules/filter/lib/Drupal/filter/Access/FormatDisableCheck.php
index d78c103..c039fba 100644
--- a/core/modules/filter/lib/Drupal/filter/Access/FormatDisableCheck.php
+++ b/core/modules/filter/lib/Drupal/filter/Access/FormatDisableCheck.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\filter\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -15,14 +15,7 @@
 /**
  * Checks access for disabling text formats.
  */
-class FormatDisableCheck implements StaticAccessCheckInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function appliesTo() {
-    return array('_filter_disable_format_access');
-  }
+class FormatDisableCheck implements AccessInterface {
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/filter/lib/Drupal/filter/FilterBag.php b/core/modules/filter/lib/Drupal/filter/FilterBag.php
index 0d521c6..2885641 100644
--- a/core/modules/filter/lib/Drupal/filter/FilterBag.php
+++ b/core/modules/filter/lib/Drupal/filter/FilterBag.php
@@ -101,8 +101,8 @@ public function sortHelper($aID, $bID) {
     if ($a->weight != $b->weight) {
       return $a->weight < $b->weight ? -1 : 1;
     }
-    if ($a->module != $b->module) {
-      return strnatcasecmp($a->module, $b->module);
+    if ($a->provider != $b->provider) {
+      return strnatcasecmp($a->provider, $b->provider);
     }
     return parent::sortHelper($aID, $bID);
   }
diff --git a/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php b/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php
index cd94f86..e01e26b 100644
--- a/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php
+++ b/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -36,10 +37,8 @@ class FilterFormatListController extends DraggableListController implements Enti
   /**
    * Constructs a new FilterFormatListController.
    *
-   * @param string $entity_type
-   *   The type of entity to be listed.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
@@ -47,8 +46,8 @@ class FilterFormatListController extends DraggableListController implements Enti
    * @param \Drupal\Core\Config\ConfigFactory $config_factory
    *   The config factory.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, ConfigFactory $config_factory) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, ConfigFactory $config_factory) {
+    parent::__construct($entity_info, $storage, $module_handler);
 
     $this->configFactory = $config_factory;
   }
@@ -56,11 +55,10 @@ public function __construct($entity_type, array $entity_info, EntityStorageContr
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
-      $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getStorageController($entity_info->id()),
       $container->get('module_handler'),
       $container->get('config.factory')
     );
diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterAutoP.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterAutoP.php
index 67cede4..3795430 100644
--- a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterAutoP.php
+++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterAutoP.php
@@ -14,7 +14,6 @@
  *
  * @Filter(
  *   id = "filter_autop",
- *   module = "filter",
  *   title = @Translation("Convert line breaks into HTML (i.e. <code>&lt;br&gt;</code> and <code>&lt;p&gt;</code>)"),
  *   type = FILTER_TYPE_MARKUP_LANGUAGE
  * )
diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterCaption.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterCaption.php
index d3552a8..c19d0be 100644
--- a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterCaption.php
+++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterCaption.php
@@ -17,7 +17,6 @@
  *
  * @Filter(
  *   id = "filter_caption",
- *   module = "filter",
  *   title = @Translation("Display image captions and align images"),
  *   description = @Translation("Uses data-caption and data-align attributes on &lt;img&gt; tags to caption and align images."),
  *   type = FILTER_TYPE_TRANSFORM_REVERSIBLE
diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtml.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtml.php
index 62c9401..b5b6bfe 100644
--- a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtml.php
+++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtml.php
@@ -14,7 +14,6 @@
  *
  * @Filter(
  *   id = "filter_html",
- *   module = "filter",
  *   title = @Translation("Limit allowed HTML tags"),
  *   type = FILTER_TYPE_HTML_RESTRICTOR,
  *   settings = {
diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlCorrector.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlCorrector.php
index 7f83026..e19071b 100644
--- a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlCorrector.php
+++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlCorrector.php
@@ -14,7 +14,6 @@
  *
  * @Filter(
  *   id = "filter_htmlcorrector",
- *   module = "filter",
  *   title = @Translation("Correct faulty and chopped off HTML"),
  *   type = FILTER_TYPE_HTML_RESTRICTOR,
  *   weight = 10
diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlEscape.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlEscape.php
index 549bcaa..6d195b7 100644
--- a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlEscape.php
+++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlEscape.php
@@ -14,7 +14,6 @@
  *
  * @Filter(
  *   id = "filter_html_escape",
- *   module = "filter",
  *   title = @Translation("Display any HTML as plain text"),
  *   type = FILTER_TYPE_HTML_RESTRICTOR,
  *   weight = -10
diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlImageSecure.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlImageSecure.php
index ba5fb26..7a0e998 100644
--- a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlImageSecure.php
+++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlImageSecure.php
@@ -14,7 +14,6 @@
  *
  * @Filter(
  *   id = "filter_html_image_secure",
- *   module = "filter",
  *   title = @Translation("Restrict images to this site"),
  *   description = @Translation("Disallows usage of &lt;img&gt; tag sources that are not hosted on this site by replacing them with a placeholder image."),
  *   type = FILTER_TYPE_HTML_RESTRICTOR,
diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterNull.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterNull.php
index a466893..90e57eb 100644
--- a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterNull.php
+++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterNull.php
@@ -18,7 +18,6 @@
  *
  * @Filter(
  *   id = "filter_null",
- *   module = "filter",
  *   title = @Translation("Provides a fallback for missing filters. Do not use."),
  *   type = FILTER_TYPE_HTML_RESTRICTOR,
  *   weight = -10
diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterUrl.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterUrl.php
index f7e2075..2398840 100644
--- a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterUrl.php
+++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterUrl.php
@@ -14,7 +14,6 @@
  *
  * @Filter(
  *   id = "filter_url",
- *   module = "filter",
  *   title = @Translation("Convert URLs into links"),
  *   type = FILTER_TYPE_MARKUP_LANGUAGE,
  *   settings = {
diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/FilterBase.php b/core/modules/filter/lib/Drupal/filter/Plugin/FilterBase.php
index 6717ecd..a9a4169 100644
--- a/core/modules/filter/lib/Drupal/filter/Plugin/FilterBase.php
+++ b/core/modules/filter/lib/Drupal/filter/Plugin/FilterBase.php
@@ -26,7 +26,7 @@
    *
    * @var string
    */
-  public $module;
+  public $provider;
 
   /**
    * A Boolean indicating whether this filter is enabled.
@@ -71,7 +71,7 @@
   public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
-    $this->module = $this->pluginDefinition['module'];
+    $this->provider = $this->pluginDefinition['provider'];
     $this->cache = $this->pluginDefinition['cache'];
 
     $this->setConfiguration($configuration);
@@ -99,7 +99,7 @@ public function setConfiguration(array $configuration) {
   public function getConfiguration() {
     return array(
       'id' => $this->getPluginId(),
-      'module' => $this->pluginDefinition['module'],
+      'provider' => $this->pluginDefinition['provider'],
       'status' => $this->status,
       'weight' => $this->weight,
       'settings' => $this->settings,
diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterDefaultConfigTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterDefaultConfigTest.php
index ef25d64..ff930c1 100644
--- a/core/modules/filter/lib/Drupal/filter/Tests/FilterDefaultConfigTest.php
+++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterDefaultConfigTest.php
@@ -63,15 +63,15 @@ function testInstallation() {
     $filters = $format->get('filters');
     $this->assertEqual($filters['filter_html_escape']['status'], 1);
     $this->assertEqual($filters['filter_html_escape']['weight'], -10);
-    $this->assertEqual($filters['filter_html_escape']['module'], 'filter');
+    $this->assertEqual($filters['filter_html_escape']['provider'], 'filter');
     $this->assertEqual($filters['filter_html_escape']['settings'], array());
     $this->assertEqual($filters['filter_autop']['status'], 1);
     $this->assertEqual($filters['filter_autop']['weight'], 0);
-    $this->assertEqual($filters['filter_autop']['module'], 'filter');
+    $this->assertEqual($filters['filter_autop']['provider'], 'filter');
     $this->assertEqual($filters['filter_autop']['settings'], array());
     $this->assertEqual($filters['filter_url']['status'], 1);
     $this->assertEqual($filters['filter_url']['weight'], 0);
-    $this->assertEqual($filters['filter_url']['module'], 'filter');
+    $this->assertEqual($filters['filter_url']['provider'], 'filter');
     $this->assertEqual($filters['filter_url']['settings'], array(
       'filter_url_length' => 72,
     ));
diff --git a/core/modules/filter/tests/filter_test/config/filter.format.filter_test.yml b/core/modules/filter/tests/filter_test/config/filter.format.filter_test.yml
index 61c97ea..9b48b54 100644
--- a/core/modules/filter/tests/filter_test/config/filter.format.filter_test.yml
+++ b/core/modules/filter/tests/filter_test/config/filter.format.filter_test.yml
@@ -10,19 +10,19 @@ langcode: en
 filters:
   filter_html_escape:
     id: filter_html_escape
-    module: filter
+    provider: filter
     status: true
     weight: -10
     settings: {  }
   filter_autop:
     id: filter_autop
-    module: filter
+    provider: filter
     status: true
     weight: 0
     settings: {  }
   filter_url:
     id: filter_url
-    module: filter
+    provider: filter
     status: true
     weight: 0
     settings:
diff --git a/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestReplace.php b/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestReplace.php
index f360050..8f5b31b 100644
--- a/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestReplace.php
+++ b/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestReplace.php
@@ -14,7 +14,6 @@
  *
  * @Filter(
  *   id = "filter_test_replace",
- *   module = "filter_test",
  *   title = @Translation("Testing filter"),
  *   description = @Translation("Replaces all content with filter and text format information."),
  *   type = FILTER_TYPE_TRANSFORM_IRREVERSIBLE
diff --git a/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestRestrictTagsAndAttributes.php b/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestRestrictTagsAndAttributes.php
index 6c94dbf..5df1bab 100644
--- a/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestRestrictTagsAndAttributes.php
+++ b/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestRestrictTagsAndAttributes.php
@@ -15,7 +15,6 @@
  *
  * @Filter(
  *   id = "filter_test_restrict_tags_and_attributes",
- *   module = "filter_test",
  *   title = @Translation("Tag and attribute restricting filter"),
  *   description = @Translation("Used for testing filter_get_html_restrictions_by_format()."),
  *   type = FILTER_TYPE_HTML_RESTRICTOR
diff --git a/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestUncacheable.php b/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestUncacheable.php
index ad2a30e..4a03e7c 100644
--- a/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestUncacheable.php
+++ b/core/modules/filter/tests/filter_test/lib/Drupal/filter_test/Plugin/Filter/FilterTestUncacheable.php
@@ -14,7 +14,6 @@
  *
  * @Filter(
  *   id = "filter_test_uncacheable",
- *   module = "filter_test",
  *   title = @Translation("Uncacheable filter"),
  *   description = @Translation("Does nothing, but makes a text format uncacheable"),
  *   type = FILTER_TYPE_TRANSFORM_IRREVERSIBLE,
diff --git a/core/modules/forum/config/node.type.forum.yml b/core/modules/forum/config/node.type.forum.yml
index 7258f39..18a8c59 100644
--- a/core/modules/forum/config/node.type.forum.yml
+++ b/core/modules/forum/config/node.type.forum.yml
@@ -9,7 +9,7 @@ settings:
   node:
     preview: 1
     options:
-      status: status
+      status: true
       # Not promoted to front page.
       promote: false
       sticky: false
diff --git a/core/modules/forum/forum.local_tasks.yml b/core/modules/forum/forum.local_tasks.yml
index 4cd9dcb..10a8f41 100644
--- a/core/modules/forum/forum.local_tasks.yml
+++ b/core/modules/forum/forum.local_tasks.yml
@@ -1,9 +1,9 @@
 forum.overview:
   route_name: forum.overview
-  tab_root_id: forum.overview
+  base_route: forum.overview
   title: List
 forum.settings:
   route_name: forum.settings
-  tab_root_id: forum.overview
+  base_route: forum.overview
   title: Settings
   weight: 100
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 95ebe76..9b405b2 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -194,10 +194,12 @@ function forum_menu_local_tasks(&$data, $route_name) {
 /**
  * Implements hook_entity_info().
  */
-function forum_entity_info(&$info) {
+function forum_entity_info(&$entity_info) {
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
   // Register forum specific form controllers.
-  $info['taxonomy_term']['controllers']['form']['forum'] = 'Drupal\forum\Form\ForumFormController';
-  $info['taxonomy_term']['controllers']['form']['container'] = 'Drupal\forum\Form\ContainerFormController';
+  $entity_info['taxonomy_term']
+    ->setForm('forum', 'Drupal\forum\Form\ForumFormController')
+    ->setForm('container', 'Drupal\forum\Form\ContainerFormController');
 }
 
 /**
diff --git a/core/modules/image/image.install b/core/modules/image/image.install
index d860e97..c48a834 100644
--- a/core/modules/image/image.install
+++ b/core/modules/image/image.install
@@ -256,7 +256,7 @@ function image_update_8002() {
 function image_update_8003() {
   $image_factory = \Drupal::service('image.factory');
   foreach (array('field', 'instance') as $type) {
-    $prefix = "field.$type";
+    $prefix = 'field.' . $type . '.';
     foreach (config_get_storage_names_with_prefix($prefix) as $config_id) {
       $config = \Drupal::config($config_id);
       $is_image = ($type == 'field' && $config->get('type') == 'image') || ($type == 'instance' && $config->get('field_type') == 'image');
diff --git a/core/modules/image/image.local_tasks.yml b/core/modules/image/image.local_tasks.yml
index 3bff783..c851144 100644
--- a/core/modules/image/image.local_tasks.yml
+++ b/core/modules/image/image.local_tasks.yml
@@ -1,9 +1,9 @@
 image.style_edit:
   title: 'Edit'
   route_name: image.style_edit
-  tab_root_id: image.style_edit
+  base_route: image.style_edit
 
 image.style_list:
   title: List
   route_name: image.style_list
-  tab_root_id: image.style_list
+  base_route: image.style_list
diff --git a/core/modules/image/image.views.inc b/core/modules/image/image.views.inc
index 0cd2c6e..083b507 100644
--- a/core/modules/image/image.views.inc
+++ b/core/modules/image/image.views.inc
@@ -39,21 +39,21 @@ function image_field_views_data(FieldInterface $field) {
 function image_field_views_data_views_data_alter(array &$data, FieldInterface $field) {
   $entity_type = $field->entity_type;
   $field_name = $field->getName();
-  $entity_info = entity_get_info($entity_type);
+  $entity_info = \Drupal::entityManager()->getDefinition($entity_type);
   $pseudo_field_name = 'reverse_' . $field_name . '_' . $entity_type;
 
   list($label) = field_views_field_label($entity_type, $field_name);
 
   $data['file_managed'][$pseudo_field_name]['relationship'] = array(
-    'title' => t('@entity using @field', array('@entity' => $entity_info['label'], '@field' => $label)),
-    'help' => t('Relate each @entity with a @field set to the image.', array('@entity' => $entity_info['label'], '@field' => $label)),
+    'title' => t('@entity using @field', array('@entity' => $entity_info->getLabel(), '@field' => $label)),
+    'help' => t('Relate each @entity with a @field set to the image.', array('@entity' => $entity_info->getLabel(), '@field' => $label)),
     'id' => 'entity_reverse',
     'field_name' => $field_name,
     'entity_type' => $entity_type,
     'field table' => FieldableDatabaseStorageController::_fieldTableName($field),
     'field field' => $field_name . '_target_id',
-    'base' => $entity_info['base_table'],
-    'base field' => $entity_info['entity_keys']['id'],
+    'base' => $entity_info->getBaseTable(),
+    'base field' => $entity_info->getKey('id'),
     'label' => t('!field_name', array('!field_name' => $field_name)),
     'join_extra' => array(
       0 => array(
diff --git a/core/modules/image/lib/Drupal/image/ImageStyleListController.php b/core/modules/image/lib/Drupal/image/ImageStyleListController.php
index 3c4f3e7..bf5618b 100644
--- a/core/modules/image/lib/Drupal/image/ImageStyleListController.php
+++ b/core/modules/image/lib/Drupal/image/ImageStyleListController.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Routing\UrlGeneratorInterface;
 use Drupal\Core\StringTranslation\Translator\TranslatorInterface;
@@ -31,10 +32,8 @@ class ImageStyleListController extends ConfigEntityListController implements Ent
   /**
    * Constructs a new ImageStyleListController object.
    *
-   * @param string $entity_type
-   *   The type of entity to be listed.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $image_style_storage
    *   The image style entity storage controller class.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
@@ -42,19 +41,18 @@ class ImageStyleListController extends ConfigEntityListController implements Ent
    * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
    *   The URL generator.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $image_style_storage, ModuleHandlerInterface $module_handler, UrlGeneratorInterface $url_generator) {
-    parent::__construct($entity_type, $entity_info, $image_style_storage, $module_handler);
+  public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $image_style_storage, ModuleHandlerInterface $module_handler, UrlGeneratorInterface $url_generator) {
+    parent::__construct($entity_info, $image_style_storage, $module_handler);
     $this->urlGenerator = $url_generator;
   }
 
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
-      $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getStorageController($entity_info->id()),
       $container->get('module_handler'),
       $container->get('url_generator'),
       $container->get('string_translation')
diff --git a/core/modules/language/language.admin.inc b/core/modules/language/language.admin.inc
index f02d7bd..2c8e7af 100644
--- a/core/modules/language/language.admin.inc
+++ b/core/modules/language/language.admin.inc
@@ -362,12 +362,12 @@ function language_content_settings_page() {
  * @ingroup forms
  */
 function language_content_settings_form(array $form, array $form_state, array $supported) {
-  $entity_info = entity_get_info();
+  $entity_info = \Drupal::entityManager()->getDefinitions();
   $labels = array();
   $default = array();
 
   foreach ($supported as $entity_type) {
-    $labels[$entity_type] = isset($entity_info[$entity_type]['label']) ? $entity_info[$entity_type]['label'] : $entity_type;
+    $labels[$entity_type] = $entity_info[$entity_type]->getLabel() ?: $entity_type;
     $default[$entity_type] = FALSE;
 
     // Check whether we have any custom setting.
@@ -407,7 +407,7 @@ function language_content_settings_form(array $form, array $form_state, array $s
       '#type' => 'container',
       '#entity_type' => $entity_type,
       '#theme' => 'language_content_settings_table',
-      '#bundle_label' => isset($info['bundle_label']) ? $info['bundle_label'] : $label,
+      '#bundle_label' => $info->getBundleLabel() ?: $label,
       '#states' => array(
         'visible' => array(
           ':input[name="entity_types[' . $entity_type . ']"]' => array('checked' => TRUE),
diff --git a/core/modules/language/language.local_tasks.yml b/core/modules/language/language.local_tasks.yml
index fc5f30c..da3a945 100644
--- a/core/modules/language/language.local_tasks.yml
+++ b/core/modules/language/language.local_tasks.yml
@@ -1,13 +1,13 @@
 language.admin_overview:
   title: 'List'
   route_name: language.admin_overview
-  tab_root_id: language.admin_overview
+  base_route: language.admin_overview
 language.negotiation:
   title: 'Detection and selection'
   route_name: language.negotiation
-  tab_root_id: language.admin_overview
+  base_route: language.admin_overview
 
 language.edit:
   title: 'Edit'
   route_name: language.edit
-  tab_root_id: language.edit
+  base_route: language.edit
diff --git a/core/modules/language/language.module b/core/modules/language/language.module
index 60f5c64..ddd0cd2 100644
--- a/core/modules/language/language.module
+++ b/core/modules/language/language.module
@@ -132,8 +132,8 @@ function language_theme() {
  */
 function language_entity_supported() {
   $supported = array();
-  foreach (entity_get_info() as $entity_type => $info) {
-    if (!empty($info['translatable'])) {
+  foreach (\Drupal::entityManager()->getDefinitions() as $entity_type => $info) {
+    if ($info->isTranslatable()) {
       $supported[$entity_type] = $entity_type;
     }
   }
diff --git a/core/modules/language/lib/Drupal/language/Form/ContentLanguageSettingsForm.php b/core/modules/language/lib/Drupal/language/Form/ContentLanguageSettingsForm.php
index a253854..6cd8514 100644
--- a/core/modules/language/lib/Drupal/language/Form/ContentLanguageSettingsForm.php
+++ b/core/modules/language/lib/Drupal/language/Form/ContentLanguageSettingsForm.php
@@ -61,7 +61,7 @@ public static function create(ContainerInterface $container) {
   protected function entitySupported() {
     $supported = array();
     foreach ($this->entityManager->getDefinitions() as $entity_type => $info) {
-      if (!empty($info['translatable'])) {
+      if ($info->isTranslatable()) {
         $supported[$entity_type] = $entity_type;
       }
     }
@@ -86,7 +86,7 @@ public function buildForm(array $form, array &$form_state) {
     $bundles = entity_get_bundles();
     $language_configuration = array();
     foreach ($this->entitySupported() as $entity_type) {
-      $labels[$entity_type] = isset($entity_info[$entity_type]['label']) ? $entity_info[$entity_type]['label'] : $entity_type;
+      $labels[$entity_type] = $entity_info[$entity_type]->getLabel() ?: $entity_type;
       $default[$entity_type] = FALSE;
 
       // Check whether we have any custom setting.
@@ -127,7 +127,7 @@ public function buildForm(array $form, array &$form_state) {
         '#type' => 'container',
         '#entity_type' => $entity_type,
         '#theme' => 'language_content_settings_table',
-        '#bundle_label' => isset($info['bundle_label']) ? $info['bundle_label'] : $label,
+        '#bundle_label' => $info->getBundleLabel() ?: $label,
         '#states' => array(
           'visible' => array(
             ':input[name="entity_types[' . $entity_type . ']"]' => array('checked' => TRUE),
diff --git a/core/modules/language/lib/Drupal/language/LanguageListController.php b/core/modules/language/lib/Drupal/language/LanguageListController.php
index 35724b7..9d65146 100644
--- a/core/modules/language/lib/Drupal/language/LanguageListController.php
+++ b/core/modules/language/lib/Drupal/language/LanguageListController.php
@@ -27,7 +27,7 @@ public function load() {
 
     // Sort the entities using the entity class's sort() method.
     // See \Drupal\Core\Config\Entity\ConfigEntityBase::sort().
-    uasort($entities, array($this->entityInfo['class'], 'sort'));
+    uasort($entities, array($this->entityInfo->getClass(), 'sort'));
     return $entities;
   }
 
diff --git a/core/modules/locale/lib/Drupal/locale/ParamConverter/LocaleAdminPathConfigEntityConverter.php b/core/modules/locale/lib/Drupal/locale/ParamConverter/LocaleAdminPathConfigEntityConverter.php
index 5061be2..1be24be 100644
--- a/core/modules/locale/lib/Drupal/locale/ParamConverter/LocaleAdminPathConfigEntityConverter.php
+++ b/core/modules/locale/lib/Drupal/locale/ParamConverter/LocaleAdminPathConfigEntityConverter.php
@@ -53,7 +53,7 @@ public function applies($definition, $name, Route $route) {
       // out whether the current entity is a ConfigEntity.
       $entity_type = substr($definition['type'], strlen('entity:'));
       $info = $this->entityManager->getDefinition($entity_type);
-      if (is_subclass_of($info['class'], '\Drupal\Core\Config\Entity\ConfigEntityInterface')) {
+      if ($info->isSubclassOf('\Drupal\Core\Config\Entity\ConfigEntityInterface')) {
         // path_is_admin() needs the path without the leading slash.
         $path = ltrim($route->getPath(), '/');
         return path_is_admin($path);
diff --git a/core/modules/locale/locale.local_tasks.yml b/core/modules/locale/locale.local_tasks.yml
index ade24ed..7644f3d 100644
--- a/core/modules/locale/locale.local_tasks.yml
+++ b/core/modules/locale/locale.local_tasks.yml
@@ -1,22 +1,22 @@
 locale.translate_page:
   route_name: locale.translate_page
-  tab_root_id: locale.translate_page
+  base_route: locale.translate_page
   title: Translate
 
 locale.translate_import:
   route_name: locale.translate_import
-  tab_root_id: locale.translate_page
+  base_route: locale.translate_page
   title: Import
   weight: 20
 
 locale.translate_export:
   route_name: locale.translate_export
-  tab_root_id: locale.translate_page
+  base_route: locale.translate_page
   title: Export
   weight: 30
 
 locale.settings:
   route_name: locale.settings
-  tab_root_id: locale.translate_page
+  base_route: locale.translate_page
   title: Settings
   weight: 100
diff --git a/core/modules/menu/menu.local_tasks.yml b/core/modules/menu/menu.local_tasks.yml
index bcf4de8..038487c 100644
--- a/core/modules/menu/menu.local_tasks.yml
+++ b/core/modules/menu/menu.local_tasks.yml
@@ -1,15 +1,15 @@
 menu.menu_edit:
   title: 'Edit menu'
   route_name: menu.menu_edit
-  tab_root_id: menu.menu_edit
+  base_route: menu.menu_edit
 
 menu.overview_page:
   title: 'List'
   route_name: menu.overview_page
-  tab_root_id: menu.overview_page
+  base_route: menu.overview_page
 
 menu.settings:
   title: 'Settings'
   route_name: menu.settings
-  tab_root_id: menu.overview_page
+  base_route: menu.overview_page
   weight: 100
diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index b82a8b4..0e5ea66 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -96,16 +96,17 @@ function menu_menu() {
  * Implements hook_entity_info().
  */
 function menu_entity_info(&$entity_info) {
-  $entity_info['menu']['controllers']['list'] = 'Drupal\menu\MenuListController';
-  $entity_info['menu']['links']['edit-form'] = 'menu.menu_edit';
-  $entity_info['menu']['controllers']['form'] = array(
-    'add' => 'Drupal\menu\MenuFormController',
-    'edit' => 'Drupal\menu\MenuFormController',
-    'delete' => 'Drupal\menu\Form\MenuDeleteForm',
-  );
-
-  $entity_info['menu_link']['controllers']['form']['delete'] = 'Drupal\menu\Form\MenuLinkDeleteForm';
-  $entity_info['menu_link']['controllers']['form']['reset'] = 'Drupal\menu\Form\MenuLinkResetForm';
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
+  $entity_info['menu']
+    ->setForm('add', 'Drupal\menu\MenuFormController')
+    ->setForm('edit', 'Drupal\menu\MenuFormController')
+    ->setForm('delete', 'Drupal\menu\Form\MenuDeleteForm')
+    ->setList('Drupal\menu\MenuListController')
+    ->setLinkTemplate('edit-form', 'menu.menu_edit');
+
+  $entity_info['menu_link']
+    ->setForm('delete', 'Drupal\menu\Form\MenuLinkDeleteForm')
+    ->setForm('reset', 'Drupal\menu\Form\MenuLinkResetForm');
 }
 
 /**
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php b/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php
index 64340e0..638f47f 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php
@@ -289,8 +289,7 @@ public function setNewRevision($value = TRUE) {
    * {@inheritdoc}
    */
   public function isNewRevision() {
-    $info = $this->entityInfo();
-    return $this->newRevision || (!empty($info['entity_keys']['revision']) && !$this->getRevisionId());
+    return $this->newRevision || ($this->entityInfo()->hasKey('revision') && !$this->getRevisionId());
   }
 
   /**
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
index 9744718..0e95f42 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\field\FieldInfo;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Cmf\Component\Routing\RouteProviderInterface;
@@ -48,10 +49,8 @@ class MenuLinkStorageController extends DatabaseStorageController implements Men
   /**
    * Overrides DatabaseStorageController::__construct().
    *
-   * @param string $entity_type
-   *   The entity type for which the instance is created.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Database\Connection $database
    *   The database connection to be used.
    * @param \Drupal\Component\Uuid\UuidInterface $uuid_service
@@ -59,8 +58,8 @@ class MenuLinkStorageController extends DatabaseStorageController implements Men
    * @param \Symfony\Cmf\Component\Routing\RouteProviderInterface $route_provider
    *   The route provider service.
    */
-  public function __construct($entity_type, array $entity_info, Connection $database, UuidInterface $uuid_service, RouteProviderInterface $route_provider) {
-    parent::__construct($entity_type, $entity_info, $database, $uuid_service);
+  public function __construct(EntityTypeInterface $entity_info, Connection $database, UuidInterface $uuid_service, RouteProviderInterface $route_provider) {
+    parent::__construct($entity_info, $database, $uuid_service);
 
     $this->routeProvider = $route_provider;
 
@@ -84,9 +83,8 @@ public function create(array $values) {
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
       $container->get('database'),
       $container->get('uuid'),
@@ -123,7 +121,7 @@ public function save(EntityInterface $entity) {
       }
 
       if ($entity->isNew()) {
-        $entity->mlid = $this->database->insert($this->entityInfo['base_table'])->fields(array('menu_name' => 'tools'))->execute();
+        $entity->mlid = $this->database->insert($this->entityInfo->getBaseTable())->fields(array('menu_name' => 'tools'))->execute();
         $entity->enforceIsNew();
       }
 
@@ -139,7 +137,7 @@ public function save(EntityInterface $entity) {
       // $entity may have additional keys left over from building a router entry.
       // The intersect removes the extra keys, allowing a meaningful comparison.
       if ($entity->isNew() || (array_intersect_key(get_object_vars($entity), get_object_vars($entity->original)) != get_object_vars($entity->original))) {
-        $return = drupal_write_record($this->entityInfo['base_table'], $entity, $this->idKey);
+        $return = drupal_write_record($this->entityInfo->getBaseTable(), $entity, $this->idKey);
 
         if ($return) {
           if (!$entity->isNew()) {
@@ -201,11 +199,11 @@ public function loadUpdatedCustomized(array $router_paths) {
       );
     $query_result = $query->execute();
 
-    if (!empty($this->entityInfo['class'])) {
+    if ($class = $this->entityInfo->getClass()) {
       // We provide the necessary arguments for PDO to create objects of the
       // specified entity class.
       // @see \Drupal\Core\Entity\EntityInterface::__construct()
-      $query_result->setFetchMode(\PDO::FETCH_CLASS, $this->entityInfo['class'], array(array(), $this->entityType));
+      $query_result->setFetchMode(\PDO::FETCH_CLASS, $class, array(array(), $this->entityType));
     }
 
     return $query_result->fetchAllAssoc($this->idKey);
@@ -281,7 +279,7 @@ public function findChildrenRelativeDepth(EntityInterface $entity) {
    * {@inheritdoc}
    */
   public function moveChildren(EntityInterface $entity) {
-    $query = $this->database->update($this->entityInfo['base_table']);
+    $query = $this->database->update($this->entityInfo->getBaseTable());
 
     $query->fields(array('menu_name' => $entity->menu_name));
 
diff --git a/core/modules/migrate/lib/Drupal/migrate/Plugin/migrate/destination/Entity.php b/core/modules/migrate/lib/Drupal/migrate/Plugin/migrate/destination/Entity.php
new file mode 100644
index 0000000..97e4121
--- /dev/null
+++ b/core/modules/migrate/lib/Drupal/migrate/Plugin/migrate/destination/Entity.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\Plugin\migrate\destination\Entity.
+ */
+
+namespace Drupal\migrate\Plugin\migrate\destination;
+
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\field\FieldInfo;
+use Drupal\migrate\Entity\Migration;
+use Drupal\migrate\Plugin\MigratePluginManager;
+use Drupal\migrate\Row;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @PluginId("entity")
+ */
+class Entity extends DestinationBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The entity storage controller.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageControllerInterface
+   */
+  protected $storageController;
+
+  /**
+   * Constructs an entity destination plugin.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param array $plugin_definition
+   *   The plugin implementation definition.
+   * @param EntityStorageControllerInterface $storage_controller
+   *   The storage controller for this entity type.
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityStorageControllerInterface $storage_controller) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->storageController = $storage_controller;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('entity.manager')->getStorageController($configuration['entity_type'])
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function import(Row $row) {
+    // @TODO: add field handling. https://drupal.org/node/2164451
+    // @TODO: add validation https://drupal.org/node/2164457
+    $entity = $this->storageController->create($row->getDestination());
+    $entity->save();
+    return array($entity->id());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIdsSchema() {
+    // TODO: Implement getIdsSchema() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields(Migration $migration = NULL) {
+    // TODO: Implement fields() method.
+  }
+
+}
diff --git a/core/modules/migrate_drupal/config/migrate.migration.d6_user_role.yml b/core/modules/migrate_drupal/config/migrate.migration.d6_user_role.yml
new file mode 100644
index 0000000..3ee7334
--- /dev/null
+++ b/core/modules/migrate_drupal/config/migrate.migration.d6_user_role.yml
@@ -0,0 +1,55 @@
+id: d6_user_role
+sourceIds:
+  rid:
+    type: int
+    "not null": true
+    default: 0
+destinationIds:
+  id:
+    type: varchar
+    length: 255
+source:
+  plugin: drupal6_user_role
+process:
+  id:
+    -
+      plugin: machine_name
+      source: name
+    -
+      plugin: dedupe_entity
+      entity_type: user_role
+      field: id
+  label: name
+# permissions start as array(array('perm' => array('perm1', 'perm2'))), array('perm' => array('perm3', 'perm4')))
+  permissions:
+    # extract gets array('perm' => array('perm1', 'perm2')) first
+    -
+      plugin: extract
+      source: permissions
+      index:
+        - perm
+    # the pipeline is now array(array('perm1', 'perm2'))
+    - plugin: flatten
+    # the pipeline is now array('perm1', 'perm2')
+    -
+      plugin: static_map
+      bypass: true
+      map:
+        'use PHP for block visibility': 'use PHP for settings'
+        'administer site-wide contact form': 'administer contact forms'
+        'post comments without approval': 'skip comment approval'
+        'edit own blog entries' : 'edit own blog content'
+        'edit any blog entry' : 'edit any blog content'
+        'delete own blog entries' : 'delete own blog content'
+        'delete any blog entry' : 'delete any blog content'
+        'create forum topics' : 'create forum content'
+        'delete any forum topic' : 'delete any forum content'
+        'delete own forum topics' : 'delete own forum content'
+        'edit any forum topic' : 'edit any forum content'
+        'edit own forum topics' : 'edit own forum content'
+    - plugin: system_update_7000
+    - plugin: node_update_7008
+    - plugin: flatten
+destination:
+  plugin: entity
+  entity_type: user_role
diff --git a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/process/d6/NodeUpdate7008.php b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/process/d6/NodeUpdate7008.php
new file mode 100644
index 0000000..06014c4
--- /dev/null
+++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/process/d6/NodeUpdate7008.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal\Plugin\migrate\Process\d6\NodeUpdate7008.
+ */
+
+namespace Drupal\migrate_drupal\Plugin\migrate\Process\d6;
+
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate\ProcessPluginBase;
+use Drupal\migrate\Row;
+
+/**
+ * Split the 'administer nodes' permission from 'access content overview'.
+ *
+ * @MigrateProcessPlugin(
+ *   id = "node_update_7008"
+ * )
+ */
+class NodeUpdate7008 extends ProcessPluginBase {
+
+  /**
+   * {@inheritdoc}
+   *
+   * Split the 'administer nodes' permission from 'access content overview'.
+   */
+  public function transform($value, MigrateExecutable $migrate_executable, Row $row, $destination_property) {
+    if ($value === 'administer nodes') {
+      return array($value, 'access content overview');
+    }
+    return $value;
+  }
+}
diff --git a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/process/d6/SystemUpdate7000.php b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/process/d6/SystemUpdate7000.php
new file mode 100644
index 0000000..aaa2dd5
--- /dev/null
+++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/process/d6/SystemUpdate7000.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal\Plugin\migrate\Process\d6\system_update_7000.
+ */
+
+namespace Drupal\migrate_drupal\Plugin\migrate\process\d6;
+
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate\ProcessPluginBase;
+use Drupal\migrate\Row;
+
+/**
+ * Rename blog and forum permissions to be consistent with other content types.
+ *
+ * @MigrateProcessPlugin(
+ *   id = "system_update_7000"
+ * )
+ */
+class SystemUpdate7000 extends ProcessPluginBase {
+
+  /**
+   * {@inheritdoc}
+   *
+   * Rename blog and forum permissions to be consistent with other content types.
+   */
+  public function transform($value, MigrateExecutable $migrate_executable, Row $row, $destination_property) {
+    $value = preg_replace('/(?<=^|,\ )create\ blog\ entries(?=,|$)/', 'create blog content', $value);
+    $value = preg_replace('/(?<=^|,\ )edit\ own\ blog\ entries(?=,|$)/', 'edit own blog content', $value);
+    $value = preg_replace('/(?<=^|,\ )edit\ any\ blog\ entry(?=,|$)/', 'edit any blog content', $value);
+    $value = preg_replace('/(?<=^|,\ )delete\ own\ blog\ entries(?=,|$)/', 'delete own blog content', $value);
+    $value = preg_replace('/(?<=^|,\ )delete\ any\ blog\ entry(?=,|$)/', 'delete any blog content', $value);
+
+    $value = preg_replace('/(?<=^|,\ )create\ forum\ topics(?=,|$)/', 'create forum content', $value);
+    $value = preg_replace('/(?<=^|,\ )delete\ any\ forum\ topic(?=,|$)/', 'delete any forum content', $value);
+    $value = preg_replace('/(?<=^|,\ )delete\ own\ forum\ topics(?=,|$)/', 'delete own forum content', $value);
+    $value = preg_replace('/(?<=^|,\ )edit\ any\ forum\ topic(?=,|$)/', 'edit any forum content', $value);
+    $value = preg_replace('/(?<=^|,\ )edit\ own\ forum\ topics(?=,|$)/', 'edit own forum content', $value);
+    return $value;
+  }
+
+}
diff --git a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/source/d6/Role.php b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/source/d6/Role.php
new file mode 100644
index 0000000..b22765b
--- /dev/null
+++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/source/d6/Role.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\Plugin\migrate\source\d6\Role.
+ */
+
+namespace Drupal\migrate_drupal\Plugin\migrate\source\d6;
+
+
+use Drupal\migrate\Row;
+
+/**
+ * Drupal 6 role source from database.
+ *
+ * @PluginId("drupal6_user_role")
+ */
+class Role extends Drupal6SqlBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    $query = $this->select('role', 'r')
+      ->fields('r', array('rid', 'name'))
+      ->orderBy('rid');
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    return array(
+      'rid' => t('Role ID.'),
+      'name' => t('The name of the user role.'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function prepareRow(Row $row, $keep = TRUE) {
+    $permissions = array();
+    $results = $this->database
+      ->select('permission', 'p', array('fetch' => \PDO::FETCH_ASSOC))
+      ->fields('p', array('pid', 'rid', 'perm', 'tid'))
+      ->condition('rid', $row->getSourceProperty('rid'))
+      ->execute();
+    foreach ($results as $perm) {
+      $permissions[] = array(
+        'pid' => $perm['pid'],
+        'rid' => $perm['rid'],
+        'perm' => array_map('trim', explode(',', $perm['perm'])),
+        'tid' => $perm['tid'],
+      );
+    }
+    $row->setSourceProperty('permissions', $permissions);
+    return parent::prepareRow($row);
+  }
+
+}
diff --git a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/Dump/Drupal6UserRole.php b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/Dump/Drupal6UserRole.php
new file mode 100644
index 0000000..934141e
--- /dev/null
+++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/Dump/Drupal6UserRole.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\Tests\Dump\Drupal6UserRole.
+ */
+
+namespace Drupal\migrate_drupal\Tests\Dump;
+use Drupal\Core\Database\Connection;
+
+/**
+ * Database dump for testing user role migration.
+ */
+class Drupal6UserRole {
+
+  /**
+   * @param \Drupal\Core\Database\Connection $database
+   */
+  public static function load(Connection $database) {
+    $database->schema()->createTable('permission', array(
+      'description' => 'Stores permissions for users.',
+      'fields' => array(
+        'pid' => array(
+          'type' => 'serial',
+          'not null' => TRUE,
+          'description' => 'Primary Key: Unique permission ID.',
+        ),
+        'rid' => array(
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+          'description' => 'The {role}.rid to which the permissions are assigned.',
+        ),
+        'perm' => array(
+          'type' => 'text',
+          'not null' => FALSE,
+          'size' => 'big',
+          'description' => 'List of permissions being assigned.',
+        ),
+        'tid' => array(
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+          'description' => 'Originally intended for taxonomy-based permissions, but never used.',
+        ),
+      ),
+      'primary key' => array('pid'),
+      'indexes' => array('rid' => array('rid')),
+    ));
+    $database->schema()->createTable('role', array(
+      'description' => 'Stores user roles.',
+      'fields' => array(
+        'rid' => array(
+          'type' => 'serial',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'description' => 'Primary Key: Unique role id.',
+        ),
+        'name' => array(
+          'type' => 'varchar',
+          'length' => 64,
+          'not null' => TRUE,
+          'default' => '',
+          'description' => 'Unique role name.',
+        ),
+      ),
+      'unique keys' => array('name' => array('name')),
+      'primary key' => array('rid'),
+    ));
+    $database->schema()->createTable('users_roles', array(
+      'description' => 'Maps users to roles.',
+      'fields' => array(
+        'uid' => array(
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+          'description' => 'Primary Key: {users}.uid for user.',
+        ),
+        'rid' => array(
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+          'description' => 'Primary Key: {role}.rid for role.',
+        ),
+      ),
+      'primary key' => array('uid', 'rid'),
+      'indexes' => array(
+        'rid' => array('rid'),
+      ),
+    ));
+    $database->insert('permission')->fields(array('pid', 'rid', 'perm'))
+      ->values(array('pid' => 3, 'rid' => 3, 'perm' => 'migrate test role 1 test permission'))
+      ->values(array('pid' => 4, 'rid' => 4, 'perm' => 'migrate test role 2 test permission'))
+      ->values(array('pid' => 5, 'rid' => 4, 'perm' => 'use PHP for settings'))
+      ->values(array('pid' => 6, 'rid' => 4, 'perm' => 'administer contact forms'))
+      ->values(array('pid' => 7, 'rid' => 4, 'perm' => 'skip comment approval'))
+      ->values(array('pid' => 8, 'rid' => 4, 'perm' => 'edit own blog content'))
+      ->values(array('pid' => 9, 'rid' => 4, 'perm' => 'edit any blog content'))
+      ->values(array('pid' => 10, 'rid' => 4, 'perm' => 'delete own blog content'))
+      ->values(array('pid' => 11, 'rid' => 4, 'perm' => 'delete any blog content'))
+      ->values(array('pid' => 12, 'rid' => 4, 'perm' => 'create forum content'))
+      ->values(array('pid' => 13, 'rid' => 4, 'perm' => 'delete any forum content'))
+      ->values(array('pid' => 14, 'rid' => 4, 'perm' => 'delete own forum content'))
+      ->values(array('pid' => 15, 'rid' => 4, 'perm' => 'edit any forum content'))
+      ->values(array('pid' => 16, 'rid' => 4, 'perm' => 'edit own forum content'))
+      ->values(array('pid' => 17, 'rid' => 4, 'perm' => 'administer nodes'))
+      ->execute();
+    $database->insert('role')->fields(array('rid', 'name'))
+      ->values(array('rid' => 3, 'name' => 'migrate test role 1'))
+      ->values(array('rid' => 4, 'name' => 'migrate test role 2'))
+      ->values(array('rid' => 5, 'name' => 'migrate test role 3'))
+      ->execute();
+    $database->insert('users_roles')->fields(array('uid', 'rid'))
+      ->values(array('uid' => 1, 'rid' => 3))
+      ->values(array('uid' => 1, 'rid' => 4))
+      ->execute();
+  }
+
+}
diff --git a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/d6/MigrateUserRoleTest.php b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/d6/MigrateUserRoleTest.php
new file mode 100644
index 0000000..5c6526b
--- /dev/null
+++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/d6/MigrateUserRoleTest.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal\Tests\d6\MigrateUserRoleTest.
+ */
+
+namespace Drupal\migrate_drupal\Tests\d6;
+
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate\MigrateMessage;
+use Drupal\migrate_drupal\Tests\MigrateDrupalTestBase;
+
+class MigrateUserRoleTest extends MigrateDrupalTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name'  => 'Migrate user roles to user.role.*.yml',
+      'description'  => 'Upgrade user roles to user.role.*.yml',
+      'group' => 'Migrate Drupal',
+    );
+  }
+
+  function testUserRole() {
+    /** @var \Drupal\migrate\entity\Migration $migration */
+    $migration = entity_load('migration', 'd6_user_role');
+    $dumps = array(
+      drupal_get_path('module', 'migrate_drupal') . '/lib/Drupal/migrate_drupal/Tests/Dump/Drupal6UserRole.php',
+    );
+    $this->prepare($migration, $dumps);
+    $executable = new MigrateExecutable($migration, new MigrateMessage());
+    $executable->import();
+
+    $rid = 'migrate_test_role_1';
+    $migrate_test_role_1 = entity_load('user_role', $rid);
+    $this->assertEqual($migrate_test_role_1->id(), $rid);
+    $this->assertEqual($migrate_test_role_1->getPermissions(), array(0 => 'migrate test role 1 test permission'));
+    $this->assertEqual(array($rid), $migration->getIdMap()->lookupDestinationID(array(3)));
+    $rid = 'migrate_test_role_2';
+    $migrate_test_role_2 = entity_load('user_role', $rid);
+    $this->assertEqual($migrate_test_role_2->getPermissions(), array(
+      'migrate test role 2 test permission',
+      'use PHP for settings',
+      'administer contact forms',
+      'skip comment approval',
+      'edit own blog content',
+      'edit any blog content',
+      'delete own blog content',
+      'delete any blog content',
+      'create forum content',
+      'delete any forum content',
+      'delete own forum content',
+      'edit any forum content',
+      'edit own forum content',
+      'administer nodes',
+      'access content overview',
+    ));
+    $this->assertEqual($migrate_test_role_2->id(), $rid);
+    $this->assertEqual(array($rid), $migration->getIdMap()->lookupDestinationID(array(4)));
+  }
+
+}
diff --git a/core/modules/migrate_drupal/tests/Drupal/migrate_drupal/Tests/source/d6/RoleSourceTest.php b/core/modules/migrate_drupal/tests/Drupal/migrate_drupal/Tests/source/d6/RoleSourceTest.php
new file mode 100644
index 0000000..0e0bf9a
--- /dev/null
+++ b/core/modules/migrate_drupal/tests/Drupal/migrate_drupal/Tests/source/d6/RoleSourceTest.php
@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal\Tests\source\d6\RoleSourceTest.
+ */
+
+namespace Drupal\migrate_drupal\Tests\source\d6;
+
+use Drupal\migrate\Tests\MigrateSqlSourceTestCase;
+
+/**
+ * Tests user role migration from D6 to D8.
+ *
+ * @group migrate_drupal
+ * @group Drupal
+ */
+class RoleSourceTest extends MigrateSqlSourceTestCase {
+
+  // The plugin system is not working during unit testing so the source plugin
+  // class needs to be manually specified.
+  const PLUGIN_CLASS = 'Drupal\migrate_drupal\Plugin\migrate\source\d6\Role';
+
+  // The fake Migration configuration entity.
+  protected $migrationConfiguration = array(
+    // The ID of the entity, can be any string.
+    'id' => 'test',
+    // Leave it empty for now.
+    'idlist' => array(),
+    // This needs to be the identifier of the actual key: rid for comment, nid
+    // for node and so on.
+    'source' => array(
+      'plugin' => 'drupal6_user_role',
+    ),
+    'sourceIds' => array(
+      'rid' => array(
+        // This is where the field schema would go but for now we need to
+        // specify the table alias for the key. Most likely this will be the
+        // same as BASE_ALIAS.
+        'alias' => 'r',
+      ),
+    ),
+    'destinationIds' => array(
+      'rid' => array(
+        // This is where the field schema would go.
+      ),
+    ),
+  );
+
+  protected $expectedResults = array(
+    array(
+      'rid' => 1,
+      'name' => 'anonymous user',
+      'permissions' => array(
+        array(
+          'pid' => 1,
+          'rid' => 1,
+          'perm' => array(
+            'access content',
+          ),
+          'tid' => 0,
+        ),
+      ),
+    ),
+    array(
+      'rid' => 2,
+      'name' => 'authenticated user',
+      'permissions' => array(
+        array(
+          'pid' => 2,
+          'rid' => 2,
+          'perm' => array(
+            'access comments',
+            'access content',
+            'post comments',
+            'post comments without approval',
+          ),
+          'tid' => 0,
+        ),
+      ),
+    ),
+    array(
+      'rid' => 3,
+      'name' => 'administrator',
+      'permissions' => array(
+        array(
+          'pid' => 3,
+          'rid' => 3,
+          'perm' => array(
+            'access comments',
+            'administer comments',
+            'post comments',
+            'post comments without approval',
+            'access content',
+            'administer content types',
+            'administer nodes',
+          ),
+          'tid' => 0,
+        ),
+      ),
+    ),
+  );
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'D6 role source functionality',
+      'description' => 'Tests D6 role source plugin.',
+      'group' => 'Migrate Drupal',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    foreach ($this->expectedResults as $k => $row) {
+      foreach ($row['permissions'] as $perm) {
+        $this->databaseContents['permission'][$perm['pid']] = $perm;
+        $this->databaseContents['permission'][$perm['pid']]['perm'] = implode(',', $perm['perm']);
+        $this->databaseContents['permission'][$perm['pid']]['rid'] = $row['rid'];
+      }
+      unset($row['permissions']);
+      $this->databaseContents['role'][$k] = $row;
+    }
+    parent::setUp();
+  }
+
+}
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\migrate_drupal\Plugin\migrate\source\d6\Role;
+
+class TestRole extends Role {
+  function setDatabase(Connection $database) {
+    $this->database = $database;
+  }
+  function setModuleHandler(ModuleHandlerInterface $module_handler) {
+    $this->moduleHandler = $module_handler;
+  }
+}
diff --git a/core/modules/node/config/schema/node.schema.yml b/core/modules/node/config/schema/node.schema.yml
index e8895cc..f2a8296 100644
--- a/core/modules/node/config/schema/node.schema.yml
+++ b/core/modules/node/config/schema/node.schema.yml
@@ -61,16 +61,16 @@ node.settings.node:
       label: 'Publishing options'
       mapping:
         status:
-          type: string
+          type: boolean
           label: 'Published'
         promote:
-          type: string
+          type: boolean
           label: 'Promoted to front page'
         sticky:
-          type: string
+          type: boolean
           label: 'Sticky at top of lists'
         revision:
-          type: string
+          type: boolean
           label: 'Create new revision'
     submitted:
       type: boolean
diff --git a/core/modules/node/lib/Drupal/node/Access/NodeAddAccessCheck.php b/core/modules/node/lib/Drupal/node/Access/NodeAddAccessCheck.php
index f67232d..8d72223 100644
--- a/core/modules/node/lib/Drupal/node/Access/NodeAddAccessCheck.php
+++ b/core/modules/node/lib/Drupal/node/Access/NodeAddAccessCheck.php
@@ -7,7 +7,8 @@
 
 namespace Drupal\node\Access;
 
-use Drupal\Core\Entity\EntityCreateAccessCheck;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -15,12 +16,24 @@
 /**
  * Determines access to for node add pages.
  */
-class NodeAddAccessCheck extends EntityCreateAccessCheck {
+class NodeAddAccessCheck implements AccessInterface {
 
   /**
-   * {@inheritdoc}
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
    */
-  protected $requirementsKey = '_node_add_access';
+  protected $entityManager;
+
+  /**
+   * Constructs a EntityCreateAccessCheck object.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   */
+  public function __construct(EntityManagerInterface $entity_manager) {
+    $this->entityManager = $entity_manager;
+  }
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/node/lib/Drupal/node/Access/NodeRevisionAccessCheck.php b/core/modules/node/lib/Drupal/node/Access/NodeRevisionAccessCheck.php
index 2d3281b..353a8fb 100644
--- a/core/modules/node/lib/Drupal/node/Access/NodeRevisionAccessCheck.php
+++ b/core/modules/node/lib/Drupal/node/Access/NodeRevisionAccessCheck.php
@@ -7,9 +7,9 @@
 
 namespace Drupal\node\Access;
 
-use Drupal\Core\Access\AccessCheckInterface;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\node\NodeInterface;
 use Symfony\Component\HttpFoundation\Request;
@@ -18,7 +18,7 @@
 /**
  * Provides an access checker for node revisions.
  */
-class NodeRevisionAccessCheck implements AccessCheckInterface {
+class NodeRevisionAccessCheck implements AccessInterface {
 
   /**
    * The node storage.
@@ -65,13 +65,6 @@ public function __construct(EntityManagerInterface $entity_manager, Connection $
   /**
    * {@inheritdoc}
    */
-  public function applies(Route $route) {
-    return array_key_exists('_access_node_revision', $route->getRequirements());
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     // If the route has a {node_revision} placeholder, load the node for that
     // revision. Otherwise, try to use a {node} placeholder.
diff --git a/core/modules/node/lib/Drupal/node/Entity/NodeType.php b/core/modules/node/lib/Drupal/node/Entity/NodeType.php
index 33105d9..cec3461 100644
--- a/core/modules/node/lib/Drupal/node/Entity/NodeType.php
+++ b/core/modules/node/lib/Drupal/node/Entity/NodeType.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\node\Entity;
 
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\node\NodeTypeInterface;
@@ -209,4 +210,26 @@ public static function postDelete(EntityStorageControllerInterface $storage_cont
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) {
+    parent::preCreate($storage_controller, $values);
+
+    // Ensure default values are set.
+    if (!isset($values['settings']['node'])) {
+      $values['settings']['node'] = array();
+    }
+    $values['settings']['node'] = NestedArray::mergeDeep(array(
+      'options' => array(
+        'status' => TRUE,
+        'promote' => TRUE,
+        'sticky' => FALSE,
+        'revision' => FALSE,
+      ),
+      'preview' => DRUPAL_OPTIONAL,
+      'submitted' => TRUE,
+    ), $values['settings']['node']);
+  }
+
 }
diff --git a/core/modules/node/lib/Drupal/node/NodeAccessController.php b/core/modules/node/lib/Drupal/node/NodeAccessController.php
index 944b411..a069854 100644
--- a/core/modules/node/lib/Drupal/node/NodeAccessController.php
+++ b/core/modules/node/lib/Drupal/node/NodeAccessController.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Database\Query\SelectInterface;
 use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Entity\EntityAccessController;
 use Drupal\Core\Entity\EntityInterface;
@@ -33,24 +34,21 @@ class NodeAccessController extends EntityAccessController implements NodeAccessC
   /**
    * Constructs a NodeAccessController object.
    *
-   * @param string $entity_type
-   *   The entity type of the access controller instance.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\node\NodeGrantDatabaseStorageInterface $grant_storage
    *   The node grant storage.
    */
-  public function __construct($entity_type, array $entity_info, NodeGrantDatabaseStorageInterface $grant_storage) {
-    parent::__construct($entity_type, $entity_info);
+  public function __construct(EntityTypeInterface $entity_info, NodeGrantDatabaseStorageInterface $grant_storage) {
+    parent::__construct($entity_info);
     $this->grantStorage = $grant_storage;
   }
 
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
       $container->get('node.grant_storage')
     );
diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php
index 32c197a..3fb7584 100644
--- a/core/modules/node/lib/Drupal/node/NodeFormController.php
+++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
@@ -44,7 +44,7 @@ protected function prepareEntity() {
       foreach (array('status', 'promote', 'sticky') as $key) {
         // Multistep node forms might have filled in something already.
         if ($node->$key->isEmpty()) {
-          $node->$key = (int) in_array($key, $this->settings['options']);
+          $node->$key = (int) !empty($this->settings['options'][$key]);
         }
       }
       $node->setAuthorId(\Drupal::currentUser()->id());
@@ -56,7 +56,7 @@ protected function prepareEntity() {
       $node->log = NULL;
     }
     // Always use the default revision setting.
-    $node->setNewRevision(in_array('revision', $this->settings['options']));
+    $node->setNewRevision(!empty($this->settings['options']['revision']));
   }
 
   /**
diff --git a/core/modules/node/lib/Drupal/node/NodeListController.php b/core/modules/node/lib/Drupal/node/NodeListController.php
index f7770d5..8d488ca 100644
--- a/core/modules/node/lib/Drupal/node/NodeListController.php
+++ b/core/modules/node/lib/Drupal/node/NodeListController.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityListController;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Core\StringTranslation\TranslationInterface;
@@ -32,21 +33,17 @@ class NodeListController extends EntityListController {
   /**
    * Constructs a new NodeListController object.
    *
-   * @param string $entity_type
-   *   The type of entity to be listed.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke hooks on.
    * @param \Drupal\Core\Datetime\Date $date_service
    *   The date service.
-   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
-   *   The string translation service.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, Date $date_service) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, Date $date_service) {
+    parent::__construct($entity_info, $storage, $module_handler);
 
     $this->dateService = $date_service;
   }
@@ -54,11 +51,10 @@ public function __construct($entity_type, array $entity_info, EntityStorageContr
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
-      $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getStorageController($entity_info->id()),
       $container->get('module_handler'),
       $container->get('date')
     );
diff --git a/core/modules/node/lib/Drupal/node/NodeTypeFormController.php b/core/modules/node/lib/Drupal/node/NodeTypeFormController.php
index 6d7d7b0..1c31b3f 100644
--- a/core/modules/node/lib/Drupal/node/NodeTypeFormController.php
+++ b/core/modules/node/lib/Drupal/node/NodeTypeFormController.php
@@ -8,7 +8,7 @@
 namespace Drupal\node;
 
 use Drupal\Core\Entity\EntityFormController;
-use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Component\Utility\MapArray;
 use Drupal\Component\Utility\String;
 
 /**
@@ -31,13 +31,8 @@ public function form(array $form, array &$form_state) {
     }
 
     $node_settings = $type->getModuleSettings('node');
-    // Ensure default settings.
-    $node_settings += array(
-      'options' => array('status', 'promote'),
-      'preview' => DRUPAL_OPTIONAL,
-      'submitted' => TRUE,
-    );
-
+    // Prepare node options to be used for 'checkboxes' form element.
+    $node_settings['options'] = MapArray::copyValuesToKeys(array_keys(array_filter($node_settings['options'])));
     $form['name'] = array(
       '#title' => t('Name'),
       '#type' => 'textfield',
diff --git a/core/modules/node/lib/Drupal/node/NodeTypeListController.php b/core/modules/node/lib/Drupal/node/NodeTypeListController.php
index 0cb406f..813f6ca 100644
--- a/core/modules/node/lib/Drupal/node/NodeTypeListController.php
+++ b/core/modules/node/lib/Drupal/node/NodeTypeListController.php
@@ -8,6 +8,7 @@
 
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
@@ -31,10 +32,8 @@ class NodeTypeListController extends ConfigEntityListController implements Entit
   /**
    * Constructs a NodeTypeFormController object.
    *
-   * @param string $entity_type
-   *   The type of entity to be listed.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
@@ -42,18 +41,18 @@ class NodeTypeListController extends ConfigEntityListController implements Entit
    * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
    *   The url generator service.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, UrlGeneratorInterface $url_generator) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, UrlGeneratorInterface $url_generator) {
+    parent::__construct($entity_info, $storage, $module_handler);
     $this->urlGenerator = $url_generator;
   }
+
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
-      $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getStorageController($entity_info->id()),
       $container->get('module_handler'),
       $container->get('url_generator')
     );
diff --git a/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportChangeTest.php b/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportChangeTest.php
index d50d6ce..bdd2b92 100644
--- a/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportChangeTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportChangeTest.php
@@ -19,7 +19,7 @@ class NodeImportChangeTest extends DrupalUnitTestBase {
    *
    * @var array
    */
-  public static $modules = array('node', 'entity', 'field', 'text', 'system', 'node_test_config');
+  public static $modules = array('node', 'entity', 'field', 'text', 'system', 'node_test_config', 'user');
 
   /**
    * Set the default field storage backend for fields created during tests.
diff --git a/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportCreateTest.php b/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportCreateTest.php
index 764d96c..c6d4b7a 100644
--- a/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportCreateTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportCreateTest.php
@@ -19,7 +19,7 @@ class NodeImportCreateTest extends DrupalUnitTestBase {
    *
    * @var array
    */
-  public static $modules = array('node', 'entity', 'field', 'text', 'system');
+  public static $modules = array('node', 'entity', 'field', 'text', 'system', 'user');
 
   /**
    * Set the default field storage backend for fields created during tests.
@@ -28,6 +28,7 @@ public function setUp() {
     parent::setUp();
 
     $this->installSchema('system', array('config_snapshot'));
+    $this->installSchema('user', array('users'));
 
     // Set default storage backend.
     $this->installConfig(array('field'));
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php
index 6b517fd..576fedf 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php
@@ -47,13 +47,14 @@ function setUp() {
   function testContentAdminSort() {
     $this->drupalLogin($this->admin_user);
 
-    // Create nodes that have different node.changed values.
-    $this->container->get('state')->set('node_test.storage_controller', TRUE);
-    \Drupal::moduleHandler()->install(array('node_test'));
     $changed = REQUEST_TIME;
     foreach (array('dd', 'aa', 'DD', 'bb', 'cc', 'CC', 'AA', 'BB') as $prefix) {
       $changed += 1000;
-      $this->drupalCreateNode(array('title' => $prefix . $this->randomName(6), 'changed' => $changed));
+      $node = $this->drupalCreateNode(array('title' => $prefix . $this->randomName(6)));
+      db_update('node_field_data')
+        ->fields(array('changed' => $changed))
+        ->condition('nid', $node->id())
+        ->execute();
     }
 
     // Test that the default sort by node.changed DESC actually fires properly.
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php
index c443f98..52dd047 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php
@@ -60,6 +60,19 @@ function testNodeCreation() {
     // Check that the node exists in the database.
     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertTrue($node, 'Node found in database.');
+
+    // Verify that pages do not show submitted information by default.
+    $submitted_by = t('Submitted by !username on !datetime', array('!username' => $this->loggedInUser->getUsername(), '!datetime' => format_date($node->getCreatedTime())));
+    $this->drupalGet('node/' . $node->id());
+    $this->assertNoText($submitted_by);
+
+    // Change the node type setting to show submitted by information.
+    $node_type = entity_load('node_type', 'page');
+    $node_type->settings['node']['submitted'] = TRUE;
+    $node_type->save();
+
+    $this->drupalGet('node/' . $node->id());
+    $this->assertText($submitted_by);
   }
 
   /**
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodePostSettingsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodePostSettingsTest.php
index 1759837..a7478a8 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodePostSettingsTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodePostSettingsTest.php
@@ -61,6 +61,7 @@ function testPagePostInfo() {
     $this->drupalPostForm('node/add/page', $edit, t('Save'));
 
     // Check that the post information is displayed.
-    $this->assertNoRaw('<span class="submitted">', 'Post information is not displayed.');
+    $elements = $this->xpath('//*[contains(@class,:class)]', array(':class' => 'submitted'));
+    $this->assertEqual(count($elements), 0, 'Post information is not displayed.');
   }
 }
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.php b/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.php
index bb06621..dcb0518 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.php
@@ -34,7 +34,13 @@ function setUp() {
 
     // Create Basic page and Article node types.
     if ($this->profile != 'standard') {
-      $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
+      $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page', 'settings' => array(
+        // Set proper default options for the page content type.
+        'node' => array(
+          'options' => array('promote' => FALSE),
+          'submitted' => FALSE,
+        ),
+      )));
       $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
     }
     $this->accessController = \Drupal::entityManager()->getAccessController('node');
diff --git a/core/modules/node/node.local_tasks.yml b/core/modules/node/node.local_tasks.yml
index 8be7fa6..d3026e1 100644
--- a/core/modules/node/node.local_tasks.yml
+++ b/core/modules/node/node.local_tasks.yml
@@ -1,30 +1,30 @@
 node.view:
   route_name: node.view
-  tab_root_id: node.view
+  base_route: node.view
   title: 'View'
 node.page_edit:
   route_name: node.page_edit
-  tab_root_id: node.view
+  base_route: node.view
   title: Edit
 node.delete_confirm:
   route_name: node.delete_confirm
-  tab_root_id: node.view
+  base_route: node.view
   title: Delete
   weight: 10
 node.content_overview:
   title: Content
   route_name: node.content_overview
-  tab_root_id: node.content_overview
+  base_route: node.content_overview
 node.revision_overview:
   route_name: node.revision_overview
-  tab_root_id: node.view
+  base_route: node.view
   title: 'Revisions'
   weight: 20
 node.type_edit:
   title: 'Edit'
   route_name: node.type_edit
-  tab_root_id: node.type_edit
+  base_route: node.type_edit
 node.overview_types:
   title: List
   route_name: node.overview_types
-  tab_root_id: node.overview_types
+  base_route: node.overview_types
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 8d1b07c..893b8dd 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -692,10 +692,11 @@ function template_preprocess_node(&$variables) {
   field_attach_preprocess($node, $variables['content'], $variables);
 
   // Display post information only on certain node types.
-  // Avoid loading the entire node type config entity here.
-  $submitted = \Drupal::config('node.type.' . $node->bundle())->get('settings.node.submitted') ?: TRUE;
-  if ($submitted) {
-    $variables['display_submitted'] = TRUE;
+  // Avoid loading the entire node type config entity here that may not exist.
+  $node_type_config = \Drupal::config('node.type.' . $node->bundle());
+  // Display submitted by default.
+  $variables['display_submitted'] = $node_type_config->isNew() || $node_type_config->get('settings.node.submitted');
+  if ($variables['display_submitted']) {
     $variables['submitted'] = t('Submitted by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
     if (theme_get_setting('features.node_user_picture')) {
       // To change user picture settings (e.g. image style), edit the 'compact'
@@ -708,7 +709,6 @@ function template_preprocess_node(&$variables) {
     }
   }
   else {
-    $variables['display_submitted'] = FALSE;
     $variables['submitted'] = '';
     $variables['user_picture'] = '';
   }
diff --git a/core/modules/node/node.services.yml b/core/modules/node/node.services.yml
index 47809f4..7329685 100644
--- a/core/modules/node/node.services.yml
+++ b/core/modules/node/node.services.yml
@@ -6,9 +6,9 @@ services:
     class: Drupal\node\Access\NodeRevisionAccessCheck
     arguments: ['@entity.manager', '@database']
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _access_node_revision }
   access_check.node.add:
     class: Drupal\node\Access\NodeAddAccessCheck
     arguments: ['@entity.manager']
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _node_add_access }
diff --git a/core/modules/node/tests/modules/node_test/lib/Drupal/node_test/NodeTest.php b/core/modules/node/tests/modules/node_test/lib/Drupal/node_test/NodeTest.php
deleted file mode 100644
index d224a90..0000000
--- a/core/modules/node/tests/modules/node_test/lib/Drupal/node_test/NodeTest.php
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\node_test\NodeTest.
- */
-
-namespace Drupal\node_test;
-
-use Drupal\node\Entity\Node;
-use Drupal\Core\Entity\EntityStorageControllerInterface;
-
-/**
- * Overrides the default node entity class for testing.
- */
-class NodeTest extends Node {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function preSave(EntityStorageControllerInterface $storage_controller) {
-    // Allow test nodes to specify their updated ('changed') time.
-  }
-
-}
diff --git a/core/modules/node/tests/modules/node_test/node_test.module b/core/modules/node/tests/modules/node_test/node_test.module
index d999462..8adfe3c 100644
--- a/core/modules/node/tests/modules/node_test/node_test.module
+++ b/core/modules/node/tests/modules/node_test/node_test.module
@@ -165,12 +165,3 @@ function node_test_node_insert(EntityInterface $node) {
     $node->save();
   }
 }
-
-/**
- * Implements hook_entity_info_alter().
- */
-function node_test_entity_info_alter(&$entity_info) {
-  if (\Drupal::state()->get('node_test.storage_controller')) {
-    $entity_info['node']['class'] = 'Drupal\node_test\NodeTest';
-  }
-}
diff --git a/core/modules/path/path.local_tasks.yml b/core/modules/path/path.local_tasks.yml
index 84e74d2..bc59857 100644
--- a/core/modules/path/path.local_tasks.yml
+++ b/core/modules/path/path.local_tasks.yml
@@ -1,4 +1,4 @@
 path.admin_overview:
   title: List
   route_name: path.admin_overview
-  tab_root_id: path.admin_overview
+  base_route: path.admin_overview
diff --git a/core/modules/picture/picture.local_tasks.yml b/core/modules/picture/picture.local_tasks.yml
index 29b8446..adf9995 100644
--- a/core/modules/picture/picture.local_tasks.yml
+++ b/core/modules/picture/picture.local_tasks.yml
@@ -1,5 +1,5 @@
 picture.mapping_page_edit:
   title: Edit
   route_name: picture.mapping_page_edit
-  tab_root_id: picture.mapping_page_edit
+  base_route: picture.mapping_page_edit
   weight: -10
diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/CrudTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/CrudTest.php
index 556d5a2..c6ca292 100644
--- a/core/modules/rdf/lib/Drupal/rdf/Tests/CrudTest.php
+++ b/core/modules/rdf/lib/Drupal/rdf/Tests/CrudTest.php
@@ -44,7 +44,7 @@ function testMappingCreation() {
     // Save bundle mapping config.
     rdf_get_mapping($this->entity_type, $this->bundle)->save();
     // Test that config file was saved.
-    $mapping_config = config_get_storage_names_with_prefix('rdf.mapping');
+    $mapping_config = config_get_storage_names_with_prefix('rdf.mapping.');
     $this->assertTrue(in_array($mapping_config_name, $mapping_config), 'Rdf mapping config saved.');
   }
 
diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/StandardProfileTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/StandardProfileTest.php
index 6f5065ec..a761cc3 100644
--- a/core/modules/rdf/lib/Drupal/rdf/Tests/StandardProfileTest.php
+++ b/core/modules/rdf/lib/Drupal/rdf/Tests/StandardProfileTest.php
@@ -282,6 +282,13 @@ protected function doArticleRdfaTests() {
    * displayed in teaser view, so it is tested in the front page tests.
    */
   protected function doPageRdfaTests() {
+    // The standard profile hides the created date on pages. Revert display to
+    // true for testing.
+    // @todo Clean-up standard profile defaults.
+    $node_type = entity_load('node_type', 'page');
+    $node_type->settings['node']['submitted'] = TRUE;
+    $node_type->save();
+
     // Feed the HTML into the parser.
     $uri_info = $this->page->uri();
     $path = $uri_info['path'];
diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module
index cfa935b..018aff6 100644
--- a/core/modules/rdf/rdf.module
+++ b/core/modules/rdf/rdf.module
@@ -16,7 +16,7 @@ function rdf_help($path, $arg) {
     case 'admin/help#rdf':
       $output = '';
       $output .= '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('The RDF module enriches your content with metadata to let other applications (e.g., search engines, aggregators, and so on) better understand its relationships and attributes. This semantically enriched, machine-readable output for Drupal sites uses the <a href="@rdfa">RDFa specification</a> which allows RDF data to be embedded in HTML markup. Other modules can define mappings of their data to RDF terms, and the RDF module makes this RDF data available to the theme. The core Drupal modules define RDF mappings for their data model, and the core Drupal themes output this RDF metadata information along with the human-readable visual information. For more information, see the online handbook entry for <a href="@rdf">RDF module</a>.', array('@rdfa' => 'http://www.w3.org/TR/xhtml-rdfa-primer/', '@rdf' => 'http://drupal.org/documentation/modules/rdf')) . '</p>';
+      $output .= '<p>' . t('The RDF module enriches your content with metadata to let other applications (e.g., search engines, aggregators, and so on) better understand its relationships and attributes. This semantically enriched, machine-readable output for Drupal sites uses the <a href="!rdfa">RDFa specification</a>, which allows RDF data to be embedded in HTML markup. Other modules can define mappings of their data to RDF terms, and the RDF module makes this RDF data available to the theme. The core Drupal modules define RDF mappings for their data model, and the core Drupal themes output this RDF metadata information along with the human-readable visual information. For more information, see the <a href="!rdf">online documentation for the RDF module</a>.', array('!rdfa' => 'http://www.w3.org/TR/xhtml-rdfa-primer/', '!rdf' => 'https://drupal.org/documentation/modules/rdf')) . '</p>';
       return $output;
   }
 }
diff --git a/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManager.php b/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManager.php
index 6802282..7e41aee 100644
--- a/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManager.php
+++ b/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManager.php
@@ -80,13 +80,11 @@ protected function writeCache() {
 
     // Type URIs correspond to bundles. Iterate through the bundles to get the
     // URI and data for them.
-    $entity_info = entity_get_info();
+    $entity_info = \Drupal::entityManager()->getDefinitions();
     foreach (entity_get_bundles() as $entity_type => $bundles) {
-      $entity_type_info = $entity_info[$entity_type];
-      $reflection = new \ReflectionClass($entity_type_info['class']);
       // Only content entities are supported currently.
       // @todo Consider supporting config entities.
-      if ($reflection->implementsInterface('\Drupal\Core\Config\Entity\ConfigEntityInterface')) {
+      if ($entity_info[$entity_type]->isSubclassOf('\Drupal\Core\Config\Entity\ConfigEntityInterface')) {
         continue;
       }
       foreach ($bundles as $bundle => $bundle_info) {
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php b/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php
index ed09ce3..3723287 100644
--- a/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php
@@ -71,8 +71,8 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
         $this->derivatives[$entity_type] = array(
           'id' => 'entity:' . $entity_type,
           'entity_type' => $entity_type,
-          'serialization_class' => $entity_info['class'],
-          'label' => $entity_info['label'],
+          'serialization_class' => $entity_info->getClass(),
+          'label' => $entity_info->getLabel(),
         );
         $this->derivatives[$entity_type] += $base_plugin_definition;
       }
diff --git a/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php b/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php
index b36d88e..2ad2400 100644
--- a/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php
+++ b/core/modules/search/lib/Drupal/search/Access/SearchAccessCheck.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\search\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\search\SearchPluginManager;
 use Symfony\Component\HttpFoundation\Request;
@@ -16,7 +16,7 @@
 /**
  * Checks access for viewing search.
  */
-class SearchAccessCheck implements StaticAccessCheckInterface {
+class SearchAccessCheck implements AccessInterface {
 
   /**
    * The search plugin manager.
@@ -38,13 +38,6 @@ public function __construct(SearchPluginManager $search_plugin_manager) {
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    return array('_search_access');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     return $this->searchManager->getActiveDefinitions() ? static::ALLOW : static::DENY;
   }
diff --git a/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php b/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php
index 9ecda1d..86e8166 100644
--- a/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php
+++ b/core/modules/search/lib/Drupal/search/Access/SearchPluginAccessCheck.php
@@ -19,13 +19,6 @@ class SearchPluginAccessCheck extends SearchAccessCheck {
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    return array('_search_plugin_view_access');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     $plugin_id = $route->getRequirement('_search_plugin_view_access');
     return $this->searchManager->pluginAccess($plugin_id, $account) ? static::ALLOW : static::DENY;
diff --git a/core/modules/search/lib/Drupal/search/Plugin/Derivative/SearchLocalTask.php b/core/modules/search/lib/Drupal/search/Plugin/Derivative/SearchLocalTask.php
index f18e699..5c418bc 100644
--- a/core/modules/search/lib/Drupal/search/Plugin/Derivative/SearchLocalTask.php
+++ b/core/modules/search/lib/Drupal/search/Plugin/Derivative/SearchLocalTask.php
@@ -26,7 +26,7 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
         $this->derivatives[$plugin_id] = array(
           'title' => $search_info['title'],
           'route_name' => 'search.view_' . $plugin_id,
-          'tab_root_id' => 'search.plugins:' . $default_info['id'],
+          'base_route' => 'search.view_' . $default_info['id'],
         );
         if ($plugin_id == $default_info['id']) {
           $this->derivatives[$plugin_id]['weight'] = -10;
diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchPageOverrideTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchPageOverrideTest.php
index 101b745..0f371e1 100644
--- a/core/modules/search/lib/Drupal/search/Tests/SearchPageOverrideTest.php
+++ b/core/modules/search/lib/Drupal/search/Tests/SearchPageOverrideTest.php
@@ -42,6 +42,7 @@ function setUp() {
   }
 
   function testSearchPageHook() {
+    $this->container->get('router.builder')->rebuild();
     $keys = 'bike shed ' . $this->randomName();
     $this->drupalGet("search/dummy_path/{$keys}");
     $this->assertText('Dummy search snippet', 'Dummy search snippet is shown');
diff --git a/core/modules/search/search.services.yml b/core/modules/search/search.services.yml
index 31dc5c5..543bd09 100644
--- a/core/modules/search/search.services.yml
+++ b/core/modules/search/search.services.yml
@@ -7,10 +7,10 @@ services:
     class: Drupal\search\Access\SearchAccessCheck
     arguments: ['@plugin.manager.search']
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _search_access }
 
   access_check.search_plugin:
     class: Drupal\search\Access\SearchPluginAccessCheck
     arguments: ['@plugin.manager.search']
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _search_plugin_view_access }
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Access/LinkAccessCheck.php b/core/modules/shortcut/lib/Drupal/shortcut/Access/LinkAccessCheck.php
new file mode 100644
index 0000000..b85fb9f
--- /dev/null
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Access/LinkAccessCheck.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\shortcut\Access\LinkAccessCheck.
+ */
+
+namespace Drupal\shortcut\Access;
+
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Provides an access check for shortcut link delete routes.
+ */
+class LinkAccessCheck implements AccessInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request, AccountInterface $account) {
+    $menu_link = $request->attributes->get('menu_link');
+    $set_name = str_replace('shortcut-', '', $menu_link['menu_name']);
+    if ($shortcut_set = shortcut_set_load($set_name)) {
+      return shortcut_set_edit_access($shortcut_set) ? static::ALLOW : static::DENY;
+    }
+    return static::DENY;
+  }
+
+}
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Access/ShortcutSetEditAccessCheck.php b/core/modules/shortcut/lib/Drupal/shortcut/Access/ShortcutSetEditAccessCheck.php
index 283825a..21bbda8 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/Access/ShortcutSetEditAccessCheck.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Access/ShortcutSetEditAccessCheck.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\shortcut\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -15,14 +15,7 @@
 /**
  * Provides an access check for shortcut link delete routes.
  */
-class ShortcutSetEditAccessCheck implements StaticAccessCheckInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function appliesTo() {
-    return array('_access_shortcut_set_edit');
-  }
+class ShortcutSetEditAccessCheck implements AccessInterface {
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Access/ShortcutSetSwitchAccessCheck.php b/core/modules/shortcut/lib/Drupal/shortcut/Access/ShortcutSetSwitchAccessCheck.php
index d39f630..6d844ed 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/Access/ShortcutSetSwitchAccessCheck.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Access/ShortcutSetSwitchAccessCheck.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\shortcut\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -15,14 +15,7 @@
 /**
  * Provides an access check for shortcut link delete routes.
  */
-class ShortcutSetSwitchAccessCheck implements StaticAccessCheckInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function appliesTo() {
-    return array('_access_shortcut_set_switch');
-  }
+class ShortcutSetSwitchAccessCheck implements AccessInterface {
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Form/SetCustomize.php b/core/modules/shortcut/lib/Drupal/shortcut/Form/SetCustomize.php
index 80e9a01..12a789c 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/Form/SetCustomize.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Form/SetCustomize.php
@@ -8,7 +8,7 @@
 namespace Drupal\shortcut\Form;
 
 use Drupal\Core\Entity\EntityFormController;
-use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\EntityManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -26,10 +26,10 @@ class SetCustomize extends EntityFormController {
   /**
    * Constructs a SetCustomize object.
    *
-   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
    */
-  public function __construct(EntityManager $entity_manager) {
+  public function __construct(EntityManagerInterface $entity_manager) {
     $this->storageController = $entity_manager->getStorageController('shortcut');
   }
 
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutAccessController.php b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutAccessController.php
index 4dd0aea..79683b8 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutAccessController.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutAccessController.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Entity\EntityAccessController;
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -28,24 +29,21 @@ class ShortcutAccessController extends EntityAccessController implements EntityC
   /**
    * Constructs a ShortcutAccessController object.
    *
-   * @param string $entity_type
-   *   The entity type of the access controller instance.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\shortcut\ShortcutSetStorageController $shortcut_set_storage
    *   The shortcut_set storage controller.
    */
-  public function __construct($entity_type, array $entity_info, ShortcutSetStorageController $shortcut_set_storage) {
-    parent::__construct($entity_type, $entity_info);
+  public function __construct(EntityTypeInterface $entity_info, ShortcutSetStorageController $shortcut_set_storage) {
+    parent::__construct($entity_info);
     $this->shortcutSetStorage = $shortcut_set_storage;
   }
 
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
       $container->get('entity.manager')->getStorageController('shortcut_set')
     );
diff --git a/core/modules/shortcut/shortcut.local_tasks.yml b/core/modules/shortcut/shortcut.local_tasks.yml
index e25ecd7..486d584 100644
--- a/core/modules/shortcut/shortcut.local_tasks.yml
+++ b/core/modules/shortcut/shortcut.local_tasks.yml
@@ -1,14 +1,14 @@
 shortcut.overview:
   route_name: shortcut.overview
-  tab_root_id: user.view
+  base_route: user.view
   title: 'Shortcuts'
 
 shortcut.set_customize:
   title: 'List links'
   route_name: shortcut.set_customize
-  tab_root_id: shortcut.set_customize
+  base_route: shortcut.set_customize
 shortcut.set_edit:
   title: 'Edit set name'
   route_name: shortcut.set_edit
-  tab_root_id: shortcut.set_customize
+  base_route: shortcut.set_customize
   weight: 10
diff --git a/core/modules/shortcut/shortcut.services.yml b/core/modules/shortcut/shortcut.services.yml
index 3a04d99..a660594 100644
--- a/core/modules/shortcut/shortcut.services.yml
+++ b/core/modules/shortcut/shortcut.services.yml
@@ -1,10 +1,15 @@
 services:
+  access_check.shortcut.link:
+    class: Drupal\shortcut\Access\LinkAccessCheck
+    tags:
+      - { name: access_check, applies_to: _access_shortcut_link }
+
   access_check.shortcut.shortcut_set_edit:
     class: Drupal\shortcut\Access\ShortcutSetEditAccessCheck
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _access_shortcut_set_edit }
 
   access_check.shortcut.shortcut_set_switch:
     class: Drupal\shortcut\Access\ShortcutSetSwitchAccessCheck
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _access_shortcut_set_switch }
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
index dee1760..879ca80 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
@@ -187,6 +187,11 @@ public function containerBuild(ContainerBuilder $container) {
       $definition = $container->getDefinition('path_processor_alias');
       $definition->clearTag('path_processor_inbound')->clearTag('path_processor_outbound');
     }
+
+    if ($container->hasDefinition('password')) {
+      $container->getDefinition('password')->setArguments(array(1));
+    }
+
     $request = Request::create('/');
     $this->container->set('request', $request);
   }
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index 70191d8..2f794b2 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -1012,14 +1012,14 @@ protected function prepareConfigDirectories() {
     include_once DRUPAL_ROOT . '/core/includes/install.inc';
     foreach (array(CONFIG_ACTIVE_DIRECTORY, CONFIG_STAGING_DIRECTORY) as $type) {
       // Assign the relative path to the global variable.
-      $path = 'simpletest/' . substr($this->databasePrefix, 10) . '/config_' . $type;
-      $GLOBALS['config_directories'][$type]['path'] = $path;
+      $path = conf_path() . '/files/simpletest/' . substr($this->databasePrefix, 10) . '/config_' . $type;
+      $GLOBALS['config_directories'][$type] = $path;
       // Ensure the directory can be created and is writeable.
       if (!install_ensure_config_directory($type)) {
         return FALSE;
       }
       // Provide the already resolved path for tests.
-      $this->configDirectories[$type] = $this->originalFileDirectory . '/' . $path;
+      $this->configDirectories[$type] = $path;
     }
   }
 
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php
index 66cc333..79b65b5 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php
@@ -85,7 +85,7 @@ function testEnableModulesInstall() {
     $this->assertFalse(in_array($module, $list), "{$module}_permission() in \Drupal::moduleHandler()->getImplementations() not found.");
 
     $this->assertFalse(db_table_exists($table), "'$table' database table not found.");
-    $schema = drupal_get_schema($table);
+    $schema = drupal_get_schema($table, TRUE);
     $this->assertFalse($schema, "'$table' table schema not found.");
 
     // Install the module.
@@ -200,30 +200,31 @@ function testInstallConfig() {
   function testEnableModulesFixedList() {
     // Install system module.
     $this->container->get('module_handler')->install(array('system'));
+    $entity_manager = \Drupal::entityManager();
 
     // entity_test is loaded via $modules; its entity type should exist.
     $this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE);
-    $this->assertTrue(TRUE == entity_get_info('entity_test'));
+    $this->assertTrue(TRUE == $entity_manager->getDefinition('entity_test'));
 
     // Load some additional modules; entity_test should still exist.
     $this->enableModules(array('entity', 'field', 'text', 'entity_test'));
     $this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE);
-    $this->assertTrue(TRUE == entity_get_info('entity_test'));
+    $this->assertTrue(TRUE == $entity_manager->getDefinition('entity_test'));
 
     // Install some other modules; entity_test should still exist.
     $this->container->get('module_handler')->install(array('field', 'field_test'), FALSE);
     $this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE);
-    $this->assertTrue(TRUE == entity_get_info('entity_test'));
+    $this->assertTrue(TRUE == $entity_manager->getDefinition('entity_test'));
 
     // Uninstall one of those modules; entity_test should still exist.
     $this->container->get('module_handler')->uninstall(array('field_test'));
     $this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE);
-    $this->assertTrue(TRUE == entity_get_info('entity_test'));
+    $this->assertTrue(TRUE == $entity_manager->getDefinition('entity_test'));
 
     // Set the weight of a module; entity_test should still exist.
     module_set_weight('entity', -1);
     $this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE);
-    $this->assertTrue(TRUE == entity_get_info('entity_test'));
+    $this->assertTrue(TRUE == $entity_manager->getDefinition('entity_test'));
 
     // Reactivate the previously uninstalled module.
     $this->enableModules(array('field_test'));
diff --git a/core/modules/simpletest/simpletest.local_tasks.yml b/core/modules/simpletest/simpletest.local_tasks.yml
index b6eea38..4f1fd00 100644
--- a/core/modules/simpletest/simpletest.local_tasks.yml
+++ b/core/modules/simpletest/simpletest.local_tasks.yml
@@ -1,9 +1,9 @@
 simpletest.test_form:
   title: List
   route_name: simpletest.test_form
-  tab_root_id: simpletest.test_form
+  base_route: simpletest.test_form
 simpletest.settings:
   title: Settings
   route_name: simpletest.settings
-  tab_root_id: simpletest.test_form
+  base_route: simpletest.test_form
   weight: 100
diff --git a/core/modules/system/entity.api.php b/core/modules/system/entity.api.php
index 5b880cd..5ed89cc 100644
--- a/core/modules/system/entity.api.php
+++ b/core/modules/system/entity.api.php
@@ -98,19 +98,19 @@ function hook_ENTITY_TYPE_create_access(\Drupal\Core\Session\AccountInterface $a
  *
  * Modules may implement this hook to add information to defined entity types.
  *
- * @param array $entity_info
+ * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
  *   An associative array of all entity type definitions, keyed by the entity
  *   type name. Passed by reference.
  *
  * @see \Drupal\Core\Entity\Entity
  * @see \Drupal\Core\Entity\EntityManagerInterface
- * @see entity_get_info()
+ * @see \Drupal\Core\Entity\EntityTypeInterface
  */
 function hook_entity_info(&$entity_info) {
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
   // Add a form controller for a custom node form without overriding the default
-  // node form. To override the default node form, use hook_entity_info_alter()
-  // to alter $entity_info['node']['controllers']['form']['default'].
-  $entity_info['node']['controllers']['form']['mymodule_foo'] = 'Drupal\mymodule\NodeFooFormController';
+  // node form. To override the default node form, use hook_entity_info_alter().
+  $entity_info['node']->setForm('mymodule_foo', 'Drupal\mymodule\NodeFooFormController');
 }
 
 /**
@@ -230,18 +230,19 @@ function hook_entity_bundle_delete($entity_type, $bundle) {
  * Do not use this hook to add information to entity types. Use
  * hook_entity_info() for that instead.
  *
- * @param array $entity_info
+ * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
  *   An associative array of all entity type definitions, keyed by the entity
  *   type name. Passed by reference.
  *
  * @see \Drupal\Core\Entity\Entity
  * @see \Drupal\Core\Entity\EntityManagerInterface
- * @see entity_get_info()
+ * @see \Drupal\Core\Entity\EntityTypeInterface
  */
 function hook_entity_info_alter(&$entity_info) {
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
   // Set the controller class for nodes to an alternate implementation of the
   // Drupal\Core\Entity\EntityStorageControllerInterface interface.
-  $entity_info['node']['controllers']['storage'] = 'Drupal\mymodule\MyCustomNodeStorageController';
+  $entity_info['node']->setController('storage', 'Drupal\mymodule\MyCustomNodeStorageController');
 }
 
 /**
diff --git a/core/modules/system/lib/Drupal/system/Access/CronAccessCheck.php b/core/modules/system/lib/Drupal/system/Access/CronAccessCheck.php
index 7b19f54..ef094fd 100644
--- a/core/modules/system/lib/Drupal/system/Access/CronAccessCheck.php
+++ b/core/modules/system/lib/Drupal/system/Access/CronAccessCheck.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\system\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -15,14 +15,7 @@
 /**
  * Access check for cron routes.
  */
-class CronAccessCheck implements StaticAccessCheckInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function appliesTo() {
-    return array('_access_system_cron');
-  }
+class CronAccessCheck implements AccessInterface {
 
   /**
    * Implements AccessCheckInterface::access().
diff --git a/core/modules/system/lib/Drupal/system/Controller/BatchController.php b/core/modules/system/lib/Drupal/system/Controller/BatchController.php
index 53e8823..2e7480d 100644
--- a/core/modules/system/lib/Drupal/system/Controller/BatchController.php
+++ b/core/modules/system/lib/Drupal/system/Controller/BatchController.php
@@ -7,6 +7,12 @@
 
 namespace Drupal\system\Controller;
 
+use Drupal\Core\Controller\TitleResolverInterface;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Page\DefaultHtmlPageRenderer;
+use Drupal\Core\Page\HtmlPage;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@@ -14,16 +20,56 @@
 /**
  * Controller routines for batch routes.
  */
-class BatchController {
+class BatchController implements ContainerInjectionInterface {
+
+  /**
+   * The HTML page renderer.
+   *
+   * @var \Drupal\Core\Page\DefaultHtmlPageRenderer
+   */
+  protected $htmlPageRenderer;
+
+  /**
+   * The title resolver.
+   *
+   * @var \Drupal\Core\Controller\TitleResolverInterface
+   */
+  protected $titleResolver;
+
+  /**
+   * Constructs a new BatchController.
+   *
+   * @param \Drupal\Core\Page\DefaultHtmlPageRenderer $html_page_renderer
+   *   The HTML page renderer.
+   * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
+   *   The title resolver.
+   */
+  public function __construct(DefaultHtmlPageRenderer $html_page_renderer, TitleResolverInterface $title_resolver) {
+    $this->htmlPageRenderer = $html_page_renderer;
+    $this->titleResolver = $title_resolver;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('html_page_renderer'),
+      $container->get('title_resolver')
+    );
+  }
 
   /**
    * Returns a system batch page.
    *
-   * @param \Symfony\Component\HttpFoundation\Request
+   * @param \Symfony\Component\HttpFoundation\Request $request
    *   The current request object.
    *
    * @return mixed
-   *   A \Symfony\Component\HttpFoundation\Response object or page element.
+   *   A \Symfony\Component\HttpFoundation\Response object or page element or
+   *   NULL.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
    */
   public function batchPage(Request $request) {
     require_once DRUPAL_ROOT . '/core/includes/batch.inc';
@@ -41,8 +87,34 @@ public function batchPage(Request $request) {
       drupal_set_page_content($output);
       $page = element_info('page');
       $page['#show_messages'] = FALSE;
+
+      $page = $this->render($page);
+
       return $page;
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function render(array $output, $status_code = 200) {
+    if (!isset($output['#title'])) {
+      $request = \Drupal::request();
+      $output['#title'] = $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT));
+    }
+    $page = new HtmlPage('', $output['#title']);
+
+    $page_array = drupal_prepare_page($output);
+
+    $page = $this->htmlPageRenderer->preparePage($page, $page_array);
+
+    $page->setBodyTop(drupal_render($page_array['page_top']));
+    $page->setBodyBottom(drupal_render($page_array['page_bottom']));
+    $page->setContent(drupal_render($page_array));
+
+    $page->setStatusCode($status_code);
+
+    return $page;
+  }
+
 }
diff --git a/core/modules/system/lib/Drupal/system/DateFormatListController.php b/core/modules/system/lib/Drupal/system/DateFormatListController.php
index d22642b..673730d 100644
--- a/core/modules/system/lib/Drupal/system/DateFormatListController.php
+++ b/core/modules/system/lib/Drupal/system/DateFormatListController.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Datetime\Date;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -30,10 +31,8 @@ class DateFormatListController extends ConfigEntityListController {
   /**
    * Constructs a new DateFormatListController object.
    *
-   * @param string $entity_type
-   *   The type of entity to be listed.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
@@ -41,8 +40,8 @@ class DateFormatListController extends ConfigEntityListController {
    * @param \Drupal\Core\Datetime\Date $date_service
    *   The date service.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, Date $date_service) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, Date $date_service) {
+    parent::__construct($entity_info, $storage, $module_handler);
 
     $this->dateService = $date_service;
   }
@@ -50,11 +49,10 @@ public function __construct($entity_type, array $entity_info, EntityStorageContr
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
-      $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getStorageController($entity_info->id()),
       $container->get('module_handler'),
       $container->get('date')
     );
diff --git a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
index b58939e..5499c94 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
@@ -326,13 +326,8 @@ public function createTmp(ImageInterface $image, $width, $height) {
    * {@inheritdoc}
    */
   public static function isAvailable() {
-    if ($check = get_extension_funcs('gd')) {
-      if (in_array('imagegd2', $check)) {
-        // GD2 support is available.
-        return TRUE;
-      }
-    }
-    return FALSE;
+    // GD2 support is available.
+    return function_exists('imagegd2');
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiInfoTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiInfoTest.php
index 4e45336..cb5ba0c 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiInfoTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiInfoTest.php
@@ -27,21 +27,21 @@ public static function getInfo() {
    */
   function testEntityInfoChanges() {
     \Drupal::moduleHandler()->install(array('entity_cache_test'));
-    $entity_info = entity_get_info();
+    $entity_info = \Drupal::entityManager()->getDefinitions();
     $this->assertTrue(isset($entity_info['entity_cache_test']), 'Test entity type found.');
 
     // Change the label of the test entity type and make sure changes appear
     // after flushing caches.
     \Drupal::state()->set('entity_cache_test.label', 'New label.');
-    $info = entity_get_info('entity_cache_test');
-    $this->assertEqual($info['label'], 'Entity Cache Test', 'Original label appears in cached entity info.');
+    $info = \Drupal::entityManager()->getDefinition('entity_cache_test');
+    $this->assertEqual($info->getLabel(), 'Entity Cache Test', 'Original label appears in cached entity info.');
     $this->resetAll();
-    $info = entity_get_info('entity_cache_test');
-    $this->assertEqual($info['label'], 'New label.', 'New label appears in entity info.');
+    $info = \Drupal::entityManager()->getDefinition('entity_cache_test');
+    $this->assertEqual($info->getLabel(), 'New label.', 'New label appears in entity info.');
 
     // Uninstall the providing module and make sure the entity type is gone.
     module_uninstall(array('entity_cache_test', 'entity_cache_test_dependency'));
-    $entity_info = entity_get_info();
+    $entity_info = \Drupal::entityManager()->getDefinitions();
     $this->assertFalse(isset($entity_info['entity_cache_test']), 'Entity type of the providing module is gone.');
   }
 
@@ -53,7 +53,7 @@ function testEntityInfoChanges() {
   function testEntityInfoCacheWatchdog() {
     \Drupal::moduleHandler()->install(array('entity_cache_test'));
     $info = \Drupal::state()->get('entity_cache_test');
-    $this->assertEqual($info['label'], 'Entity Cache Test', 'Entity info label is correct.');
-    $this->assertEqual($info['controllers']['storage'], 'Drupal\Core\Entity\DatabaseStorageController', 'Entity controller class info is correct.');
+    $this->assertEqual($info->getLabel(), 'Entity Cache Test', 'Entity info label is correct.');
+    $this->assertEqual($info->getController('storage'), 'Drupal\Core\Entity\DatabaseStorageController', 'Entity controller class info is correct.');
   }
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiTest.php
index 41cab33..a9f0105 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiTest.php
@@ -94,12 +94,12 @@ protected function assertCRUD($entity_type, UserInterface $user1) {
 
     // Verify that all data got deleted.
     $definition = \Drupal::entityManager()->getDefinition($entity_type);
-    $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {' . $definition['base_table'] . '}')->fetchField(), 'Base table was emptied');
-    if (isset($definition['data_table'])) {
-      $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {' . $definition['data_table'] . '}')->fetchField(), 'Data table was emptied');
+    $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {' . $definition->getBaseTable() . '}')->fetchField(), 'Base table was emptied');
+    if ($data_table = $definition->getDataTable()) {
+      $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {' . $data_table . '}')->fetchField(), 'Data table was emptied');
     }
-    if (isset($definition['revision_table'])) {
-      $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {' . $definition['revision_table'] . '}')->fetchField(), 'Data table was emptied');
+    if ($revision_table = $definition->getRevisionTable()) {
+      $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {' . $revision_table . '}')->fetchField(), 'Data table was emptied');
     }
   }
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php
index 89b9645..805ee22 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php
@@ -44,14 +44,8 @@ public static function getInfo() {
   }
 
   protected function checkRequirements() {
-    $gd_available = FALSE;
-    if ($check = get_extension_funcs('gd')) {
-      if (in_array('imagegd2', $check)) {
-        // GD2 support is available.
-        $gd_available =  TRUE;
-      }
-    }
-    if (!$gd_available) {
+    // GD2 support is available.
+    if (!function_exists('imagegd2')) {
       return array(
         'Image manipulations for the GD toolkit cannot run because the GD toolkit is not available.',
       );
diff --git a/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php b/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php
index 91e5485..addfc9e 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Installer/InstallerTranslationTest.php
@@ -108,9 +108,8 @@ protected function setUp() {
 
     // Reload config directories.
     include $this->public_files_directory . '/settings.php';
-    $prefix = substr($this->public_files_directory, strlen(conf_path() . '/files/'));
-    foreach ($config_directories as $type => $data) {
-      $GLOBALS['config_directories'][$type]['path'] = $prefix . '/files/' . $data['path'];
+    foreach ($config_directories as $type => $path) {
+      $GLOBALS['config_directories'][$type] = $path;
     }
     $this->rebuildContainer();
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php b/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php
index df8cec1..cda3537 100644
--- a/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php
@@ -82,9 +82,8 @@ protected function setUp() {
     $this->drupalPostForm(NULL, array(), 'Save and continue');
     // Reload config directories.
     include $this->public_files_directory . '/settings.php';
-    $prefix = substr($this->public_files_directory, strlen(conf_path() . '/files/'));
-    foreach ($config_directories as $type => $data) {
-      $GLOBALS['config_directories'][$type]['path'] = $prefix . '/files/' . $data['path'];
+    foreach ($config_directories as $type => $path) {
+      $GLOBALS['config_directories'][$type] = $path;
     }
     $this->rebuildContainer();
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/Mail/MailTest.php b/core/modules/system/lib/Drupal/system/Tests/Mail/MailTest.php
index 4326c96..927b7b1 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Mail/MailTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Mail/MailTest.php
@@ -78,24 +78,29 @@ public function testCancelMessage() {
   }
 
   /**
-   * Checks for the site name in an auto-generated From: header.
+   * Checks the From: and Reply-to: headers.
    */
-  function testFromHeader() {
+  public function testFromAndReplyToHeader() {
     global $language;
 
     // Reset the class variable holding a copy of the last sent message.
     self::$sent_message = NULL;
-    // Send an e-mail with a sender address specified.
-    $from_email = 'someone_else@example.com';
-    drupal_mail('simpletest', 'from_test', 'from_test@example.com', $language, array(), $from_email);
-    // Test that the from e-mail is just the e-mail and not the site name and
+    // Send an e-mail with a reply-to address specified.
+    $from_email = 'Drupal <simpletest@example.com>';
+    $reply_email = 'someone_else@example.com';
+    drupal_mail('simpletest', 'from_test', 'from_test@example.com', $language, array(), $reply_email);
+    // Test that the reply-to e-mail is just the e-mail and not the site name and
     // default sender e-mail.
-    $this->assertEqual($from_email, self::$sent_message['headers']['From']);
+    $this->assertEqual($from_email, self::$sent_message['headers']['From'], 'Message is sent from the site email account.');
+    $this->assertEqual($reply_email, self::$sent_message['headers']['Reply-to'], 'Message reply-to headers are set.');
+    $this->assertFalse(isset(self::$sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.');
 
     self::$sent_message = NULL;
     // Send an e-mail and check that the From-header contains the site name.
     drupal_mail('simpletest', 'from_test', 'from_test@example.com', $language);
-    $this->assertEqual('Drupal <simpletest@example.com>', self::$sent_message['headers']['From']);
+    $this->assertEqual($from_email, self::$sent_message['headers']['From'], 'Message is sent from the site email account.');
+    $this->assertFalse(isset(self::$sent_message['headers']['Reply-to']), 'Message reply-to is not set if not specified.');
+    $this->assertFalse(isset(self::$sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.');
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/System/PageTitleTest.php b/core/modules/system/lib/Drupal/system/Tests/System/PageTitleTest.php
index bafa68d..33f2f47 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/PageTitleTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/PageTitleTest.php
@@ -135,13 +135,6 @@ public function testRoutingTitle() {
     $result = $this->xpath('//h1');
     $this->assertEqual('Foo', (string) $result[0]);
 
-    // Test a controller using _controller instead of _content.
-    $this->drupalGet('test-render-title-controller');
-
-    $this->assertTitle('Foo | Drupal');
-    $result = $this->xpath('//h1');
-    $this->assertEqual('Foo', (string) $result[0]);
-
     // Test forms
     $this->drupalGet('form-test/object-builder');
 
diff --git a/core/modules/system/system.local_tasks.yml b/core/modules/system/system.local_tasks.yml
index 0ea4ef3..4d597ee 100644
--- a/core/modules/system/system.local_tasks.yml
+++ b/core/modules/system/system.local_tasks.yml
@@ -1,67 +1,65 @@
 system.rss_feeds_settings_tab:
   route_name: system.rss_feeds_settings
   title: Settings
-  tab_root_id: system.rss_feeds_settings_tab
+  base_route: system.rss_feeds_settings
 
 system.site_maintenance_mode_tab:
   route_name: system.site_maintenance_mode
   title: Settings
-  tab_root_id: system.site_maintenance_mode_tab
+  base_route: system.site_maintenance_mode
 
 system.site_information_settings_tab:
   route_name: system.site_information_settings
   title: Settings
-  tab_root_id: system.site_information_settings_tab
+  base_route: system.site_information_settings
 
 system.themes_page:
   route_name: system.themes_page
   title: 'List'
-  tab_root_id: system.themes_page
+  base_route: system.themes_page
 system.theme_settings:
   route_name: system.theme_settings
   title: 'Settings'
-  tab_root_id: system.themes_page
+  base_route: system.themes_page
   weight: 100
 
 system.theme_settings_global:
   route_name: system.theme_settings
   title: 'Global settings'
-  tab_root_id: system.themes_page
-  tab_parent_id: system.theme_settings
+  parent_id: system.theme_settings
   weight: -100
 system.theme_settings_theme:
   route_name: system.theme_settings_theme
   title: 'Theme name'
-  tab_root_id: system.themes_page
-  tab_parent_id: system.theme_settings
+  parent_id: system.theme_settings
   derivative: Drupal\system\Plugin\Derivative\ThemeLocalTask
 
 system.modules_list:
   route_name: system.modules_list
-  tab_root_id: system.modules_list
+  base_route: system.modules_list
   title: 'List'
 system.modules_uninstall:
   route_name: system.modules_uninstall
-  tab_root_id: system.modules_list
+  base_route: system.modules_list
   title: 'Uninstall'
   weight: 20
 
 system.admin:
   route_name: system.admin
-  tab_root_id: system.admin
+  base_route: system.admin
   title: 'Tasks'
   weight: -20
 system.admin_index:
   route_name: system.admin_index
-  tab_root_id: system.admin
+  base_route: system.admin
   title: 'Index'
   weight: -18
 
 system.date_format_list:
   route_name: system.date_format_list
-  tab_root_id: system.date_format_list
+  base_route: system.date_format_list
   title: 'List'
 system.date_format_edit:
   title: 'Edit'
   route_name: system.date_format_edit
-  tab_root_id: system.date_format_edit
+  base_route: system.date_format_edit
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index f4bf194..ad5f7f3 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -257,7 +257,6 @@ function system_element_info() {
     '#post_render' => array('drupal_post_render_cache_tags_page_set'),
     '#show_messages' => TRUE,
     '#theme' => 'page',
-    '#theme_wrappers' => array('html'),
   );
   // By default, we don't want Ajax commands being rendered in the context of an
   // HTML page, so we don't provide defaults for #theme or #theme_wrappers.
@@ -2112,6 +2111,19 @@ function system_page_build(&$page) {
 }
 
 /**
+ * Implements hook_custom_theme().
+ */
+function system_custom_theme() {
+  if (drupal_container()->isScopeActive('request')) {
+    $request = \Drupal::request();
+    $path = $request->attributes->get('_system_path');
+    if (user_access('view the administration theme') && path_is_admin($path)) {
+      return \Drupal::config('system.theme')->get('admin');
+    }
+  }
+}
+
+/**
  * Implements hook_form_FORM_ID_alter().
  */
 function system_form_user_form_alter(&$form, &$form_state) {
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 25108ce..4d56d47 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -377,12 +377,21 @@ system.admin_config:
   requirements:
     _permission: 'access administration pages'
 
-system.batch_page:
+# @todo system.batch_page.html does not work due to some ordering issues.
+system.batch_page.normal:
+  path: '/batch'
+  defaults:
+    _content: '\Drupal\system\Controller\BatchController::batchPage'
+  requirements:
+    _access: 'TRUE'
+
+system.batch_page.json:
   path: '/batch'
   defaults:
     _controller: '\Drupal\system\Controller\BatchController::batchPage'
   requirements:
     _access: 'TRUE'
+    _format: 'json'
 
 system.update:
   path: '/core/update.php'
diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml
index 3dcbb84..54e5b11 100644
--- a/core/modules/system/system.services.yml
+++ b/core/modules/system/system.services.yml
@@ -2,7 +2,7 @@ services:
   access_check.cron:
     class: Drupal\system\Access\CronAccessCheck
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _access_system_cron }
   system.manager:
     class: Drupal\system\SystemManager
     arguments: ['@module_handler', '@database', '@entity.manager']
diff --git a/core/modules/system/tests/modules/batch_test/batch_test.local_tasks.yml b/core/modules/system/tests/modules/batch_test/batch_test.local_tasks.yml
index ea4a6fd..f2a8525 100644
--- a/core/modules/system/tests/modules/batch_test/batch_test.local_tasks.yml
+++ b/core/modules/system/tests/modules/batch_test/batch_test.local_tasks.yml
@@ -1,46 +1,46 @@
 batch_test.test_form:
   title: Simple
   route_name: batch_test.test_form
-  tab_root_id: batch_test.test_form
+  base_route: batch_test.test_form
 
 batch_test.multistep:
   title: Multistep
   route_name: batch_test.multistep
-  tab_root_id: batch_test.test_form
+  base_route: batch_test.test_form
   weight: 1
 
 batch_test.chained:
   title: Chained
   route_name: batch_test.chained
-  tab_root_id: batch_test.test_form
+  base_route: batch_test.test_form
   weight: 2
 
 batch_test.programmatic:
   title: Chained
   route_name: batch_test.programmatic
-  tab_root_id: batch_test.test_form
+  base_route: batch_test.test_form
   weight: 3
 
 batch_test.no_form:
   title: 'No form'
   route_name: batch_test.no_form
-  tab_root_id: batch_test.test_form
+  base_route: batch_test.test_form
   weight: 4
 
 batch_test.large_percentage:
   title: 'Large percentage'
   route_name: batch_test.large_percentage
-  tab_root_id: batch_test.test_form
+  base_route: batch_test.test_form
   weight: 5
 
 batch_test.nested_programmatic:
   title: 'Nested programmatic'
   route_name: batch_test.nested_programmatic
-  tab_root_id: batch_test.test_form
+  base_route: batch_test.test_form
   weight: 6
 
 batch_test.redirect:
   title: 'Redirect'
   route_name: batch_test.redirect
-  tab_root_id: batch_test.test_form
+  base_route: batch_test.test_form
   weight: 7
diff --git a/core/modules/system/tests/modules/entity_cache_test/entity_cache_test.module b/core/modules/system/tests/modules/entity_cache_test/entity_cache_test.module
index 91117b8..9b28815 100644
--- a/core/modules/system/tests/modules/entity_cache_test/entity_cache_test.module
+++ b/core/modules/system/tests/modules/entity_cache_test/entity_cache_test.module
@@ -18,7 +18,7 @@
  */
 function entity_cache_test_watchdog($log_entry) {
   if ($log_entry['type'] == 'system' && $log_entry['message'] == '%module module installed.') {
-    $info = entity_get_info('entity_cache_test');
+    $info = \Drupal::entityManager()->getDefinition('entity_cache_test');
     // Store the information in a system variable to analyze it later in the
     // test case.
     \Drupal::state()->set('entity_cache_test', $info);
diff --git a/core/modules/system/tests/modules/entity_cache_test_dependency/entity_cache_test_dependency.module b/core/modules/system/tests/modules/entity_cache_test_dependency/entity_cache_test_dependency.module
index 004f531..61dc196 100644
--- a/core/modules/system/tests/modules/entity_cache_test_dependency/entity_cache_test_dependency.module
+++ b/core/modules/system/tests/modules/entity_cache_test_dependency/entity_cache_test_dependency.module
@@ -8,6 +8,7 @@
 /**
  * Implements hook_entity_info_alter().
  */
-function entity_cache_test_dependency_entity_info_alter(&$info) {
-  $info['entity_cache_test']['label'] = \Drupal::state()->get('entity_cache_test.label') ?: 'Entity Cache Test';
+function entity_cache_test_dependency_entity_info_alter(&$entity_info) {
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
+  $entity_info['entity_cache_test']->set('label', \Drupal::state()->get('entity_cache_test.label') ?: 'Entity Cache Test');
 }
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module
index d16c503..d552d1d 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -60,11 +60,14 @@ function entity_test_entity_types($filter = NULL) {
 /**
  * Implements hook_entity_info_alter().
  */
-function entity_test_entity_info_alter(&$info) {
+function entity_test_entity_info_alter(&$entity_info) {
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
   foreach (entity_test_entity_types() as $entity_type) {
     // Optionally specify a translation handler for testing translations.
     if (\Drupal::state()->get('entity_test.translation')) {
-      $info[$entity_type]['translation'][$entity_type] = TRUE;
+      $translation = $entity_info[$entity_type]->get('translation');
+      $translation[$entity_type] = TRUE;
+      $entity_info[$entity_type]->set('translation', $translation);
     }
   }
 }
@@ -142,9 +145,9 @@ function entity_test_delete_bundle($bundle, $entity_type = 'entity_test') {
  */
 function entity_test_entity_bundle_info() {
   $bundles = array();
-  $entity_info = entity_get_info();
+  $entity_info = \Drupal::entityManager()->getDefinitions();
   foreach ($entity_info as $entity_type => $info) {
-    if ($info['provider'] == 'entity_test') {
+    if ($info->getProvider() == 'entity_test') {
       $bundles[$entity_type] = \Drupal::state()->get($entity_type . '.bundles') ?: array($entity_type => array('label' => 'Entity Test Bundle'));
     }
   }
@@ -155,9 +158,9 @@ function entity_test_entity_bundle_info() {
  * Implements hook_entity_view_mode_info_alter().
  */
 function entity_test_entity_view_mode_info_alter(&$view_modes) {
-  $entity_info = entity_get_info();
+  $entity_info = \Drupal::entityManager()->getDefinitions();
   foreach ($entity_info as $entity_type => $info) {
-    if ($entity_info[$entity_type]['provider'] == 'entity_test' && !isset($view_modes[$entity_type])) {
+    if ($entity_info[$entity_type]->getProvider() == 'entity_test' && !isset($view_modes[$entity_type])) {
       $view_modes[$entity_type] = array(
         'full' => array(
           'label' => t('Full object'),
@@ -178,9 +181,9 @@ function entity_test_entity_view_mode_info_alter(&$view_modes) {
  * Implements hook_entity_form_mode_info_alter().
  */
 function entity_test_entity_form_mode_info_alter(&$form_modes) {
-  $entity_info = entity_get_info();
+  $entity_info = \Drupal::entityManager()->getDefinitions();
   foreach ($entity_info as $entity_type => $info) {
-    if ($entity_info[$entity_type]['provider'] == 'entity_test') {
+    if ($entity_info[$entity_type]->getProvider() == 'entity_test') {
       $form_modes[$entity_type] = array(
         'compact' => array(
           'label' => t('Compact version'),
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php
index 6ca66a4..1193cd5 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php
@@ -112,7 +112,7 @@ public function label($langcode = NULL) {
     if (!isset($langcode)) {
       $langcode = $this->activeLangcode;
     }
-    if (isset($info['entity_keys']['label']) && $info['entity_keys']['label'] == 'name') {
+    if ($info->getKey('laebl') == 'name') {
       return $this->getTranslation($langcode)->name->value;
     }
     else {
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php
index 7fe2812..89fe5ac 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php
@@ -50,7 +50,7 @@ public function form(array $form, array &$form_state) {
 
     // @todo: Is there a better way to check if an entity type is revisionable?
     $entity_info = $entity->entityInfo();
-    if (!empty($entity_info['entity_keys']['revision']) && !$entity->isNew()) {
+    if ($entity_info->hasKey('revision') && !$entity->isNew()) {
       $form['revision'] = array(
         '#type' => 'checkbox',
         '#title' => t('Create new revision'),
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Derivative/EntityTestLocalTasks.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Derivative/EntityTestLocalTasks.php
index c84a2d4..b9bbefb 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Derivative/EntityTestLocalTasks.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Derivative/EntityTestLocalTasks.php
@@ -23,7 +23,7 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
 
     foreach($types as $entity_type) {
       $this->derivatives[$entity_type] = array();
-      $this->derivatives[$entity_type]['tab_root_id'] = "entity_test.local_tasks:$entity_type";
+      $this->derivatives[$entity_type]['base_route'] = "entity_test.edit_$entity_type";
       $this->derivatives[$entity_type]['route_name'] = "entity_test.edit_$entity_type";
       $this->derivatives[$entity_type]['title'] = 'Edit';
     }
diff --git a/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/EventSubscriber/ActiveTrailTestSubscriber.php b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/EventSubscriber/ActiveTrailTestSubscriber.php
new file mode 100644
index 0000000..c7d622d
--- /dev/null
+++ b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/EventSubscriber/ActiveTrailTestSubscriber.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_test\EventSubscriber\ActiveTrailTestSubscriber.
+ */
+
+namespace Drupal\menu_test\EventSubscriber;
+
+use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\Event\KernelEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Tracks the active trail.
+ */
+class ActiveTrailTestSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The state keyvalue store.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   */
+  protected $state;
+
+  /**
+   * Constructs the ActiveTrailTestSubscriber.
+   *
+   * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $state
+   *   The state keyvalue store.
+   */
+  public function __construct(KeyValueStoreInterface $state) {
+    $this->state = $state;
+  }
+
+  /**
+   * Records the active trail.
+   */
+  public function onKernelException(KernelEvent $event) {
+    // When requested by one of the MenuTrailTestCase tests, record the initial
+    // active trail during Drupal's bootstrap (before the user is redirected to a
+    // custom 403 or 404 page). See menu_test_custom_403_404_callback().
+    if ($this->state->get('menu_test.record_active_trail') ?: FALSE) {
+      $this->state->set('menu_test.active_trail_initial', menu_get_active_trail());
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    // The actual exception subscriber has a weight of -128 so this comes first.
+    $events[KernelEvents::EXCEPTION] = 'onKernelException';
+    return $events;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/menu_test/menu_test.local_tasks.yml b/core/modules/system/tests/modules/menu_test/menu_test.local_tasks.yml
index 2947bf7..eaa9aae 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.local_tasks.yml
+++ b/core/modules/system/tests/modules/menu_test/menu_test.local_tasks.yml
@@ -1,73 +1,69 @@
 menu_test.local_task_test_tasks_view:
   route_name: menu_test.local_task_test_tasks_view
   title: View
-  tab_root_id: menu_test.local_task_test_tasks_view
+  base_route: menu_test.local_task_test_tasks_view
 menu_test.local_task_test_tasks_edit:
   route_name: menu_test.local_task_test_tasks_edit
   title: Edit
-  tab_root_id: menu_test.local_task_test_tasks_view
+  base_route: menu_test.local_task_test_tasks_view
 menu_test.local_task_test_tasks_settings:
   route_name: menu_test.local_task_test_tasks_settings
   title: Settings
-  tab_root_id: menu_test.local_task_test_tasks_view
+  base_route: menu_test.local_task_test_tasks_view
 menu_test.local_task_test_tasks_settings_sub1:
   route_name: menu_test.local_task_test_tasks_settings_sub1
   title: sub1
-  tab_root_id: menu_test.local_task_test_tasks_view
-  tab_parent_id: menu_test.local_task_test_tasks_settings
+  parent_id: menu_test.local_task_test_tasks_settings
   class: Drupal\menu_test\Plugin\Menu\LocalTask\TestTasksSettingsSub1
   weight: -10
 menu_test.local_task_test_tasks_settings_sub2:
   route_name: menu_test.local_task_test_tasks_settings_sub2
   title: sub2
-  tab_root_id: menu_test.local_task_test_tasks_view
-  tab_parent_id: menu_test.local_task_test_tasks_settings
+  parent_id: menu_test.local_task_test_tasks_settings
 menu_test.local_task_test_tasks_settings_sub3:
   route_name: menu_test.local_task_test_tasks_settings_sub3
   title: sub3
-  tab_root_id: menu_test.local_task_test_tasks_view
-  tab_parent_id: menu_test.local_task_test_tasks_settings
+  parent_id: menu_test.local_task_test_tasks_settings
   weight: 20
 menu_test.local_task_test_tasks_settings_derived:
   route_name: menu_test.local_task_test_tasks_settings_derived
   title: derived
-  tab_root_id: menu_test.local_task_test_tasks_view
-  tab_parent_id: menu_test.local_task_test_tasks_settings
+  parent_id: menu_test.local_task_test_tasks_settings
   derivative: Drupal\menu_test\Plugin\Derivative\LocalTaskTest
   weight: 50
 menu_test.local_task_test.placeholder_sub1:
   route_name: menu_test.local_task_test_placeholder_sub1
   title: 'placeholder sub1'
-  tab_root_id: menu_test.local_task_test_placeholder_sub1
+  base_route: menu_test.local_task_test_placeholder_sub1
 menu_test.local_task_test_placeholder_sub2:
   route_name: menu_test.local_task_test_placeholder_sub2
   title: 'placeholder sub2'
-  tab_root_id: menu_test.local_task_test_placeholder_sub1
+  base_route: menu_test.local_task_test_placeholder_sub1
 menu_test.local_task_test.upcasting_sub1:
   route_name: menu_test.local_task_test_upcasting_sub1
   title: 'upcasting sub1'
-  tab_root_id: menu_test.local_task_test_upcasting_sub1
+  base_route: menu_test.local_task_test_upcasting_sub1
 menu_test.local_task_test_upcasting_sub2:
   route_name: menu_test.local_task_test_upcasting_sub2
   title: 'upcasting sub2'
-  tab_root_id: menu_test.local_task_test_upcasting_sub1
+  base_route: menu_test.local_task_test_upcasting_sub1
   weight: 10
 
 menu_test.tasks_default_tab:
   route_name: menu_test.tasks_default
   title: 'View'
-  tab_root_id: menu_test.tasks_default_tab
+  base_route: menu_test.tasks_default
 
 menu_test.tasks_tasks_tab:
   route_name: menu_test.tasks_tasks
   title: 'View'
-  tab_root_id: menu_test.tasks_tasks_tab
+  base_route: menu_test.tasks_tasks
 menu_test.tasks_edit_tab:
   route_name: menu_test.tasks_edit
   title: 'Edit'
-  tab_root_id: menu_test.tasks_tasks_tab
+  base_route: menu_test.tasks_tasks
 menu_test.tasks_settings_tab:
   route_name: menu_test.tasks_settings
   title: 'Settings'
   weight: 100
-  tab_root_id: menu_test.tasks_tasks_tab
+  base_route: menu_test.tasks_tasks
diff --git a/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/Annotation/PluginExample.php b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/Annotation/PluginExample.php
index f830152..155e25d 100644
--- a/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/Annotation/PluginExample.php
+++ b/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test/Plugin/Annotation/PluginExample.php
@@ -7,14 +7,14 @@
 
 namespace Drupal\plugin_test\Plugin\Annotation;
 
-use Drupal\Component\Annotation\AnnotationInterface;
+use Drupal\Component\Annotation\AnnotationBase;
 
 /**
  * Defines a custom Plugin annotation.
  *
  * @Annotation
  */
-class PluginExample implements AnnotationInterface {
+class PluginExample extends AnnotationBase {
 
   /**
    * The plugin ID.
@@ -37,6 +37,8 @@ public function get() {
     return array(
       'id' => $this->id,
       'custom' => $this->custom,
+      'class' => $this->class,
+      'provider' => $this->provider,
     );
   }
 
diff --git a/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/Access/DefinedTestAccessCheck.php b/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/Access/DefinedTestAccessCheck.php
index 7abafd4..7aa50dc 100644
--- a/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/Access/DefinedTestAccessCheck.php
+++ b/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/Access/DefinedTestAccessCheck.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\router_test\Access;
 
-use Drupal\Core\Access\AccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
@@ -15,14 +15,7 @@
 /**
  * Defines an access checker similar to DefaultAccessCheck
  */
-class DefinedTestAccessCheck implements AccessCheckInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function applies(Route $route) {
-    return array_key_exists('_test_access', $route->getRequirements());
-  }
+class DefinedTestAccessCheck implements AccessInterface {
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/Access/TestAccessCheck.php b/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/Access/TestAccessCheck.php
index 2f3664a..eda7560 100644
--- a/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/Access/TestAccessCheck.php
+++ b/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/Access/TestAccessCheck.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\router_test\Access;
 
-use Drupal\Core\Access\AccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -15,14 +15,7 @@
 /**
  * Access check for test routes.
  */
-class TestAccessCheck implements AccessCheckInterface {
-
-  /**
-   * Implements AccessCheckInterface::applies().
-   */
-  public function applies(Route $route) {
-    return array_key_exists('_access_router_test', $route->getRequirements());
-  }
+class TestAccessCheck implements AccessInterface {
 
   /**
    * Implements AccessCheckInterface::access().
diff --git a/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/RouteTestSubscriber.php b/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/RouteTestSubscriber.php
index e342209..39fa645 100644
--- a/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/RouteTestSubscriber.php
+++ b/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/RouteTestSubscriber.php
@@ -21,7 +21,7 @@ protected function alterRoutes(RouteCollection $collection, $provider) {
     if ($provider == 'router_test') {
       $route = $collection->get('router_test.6');
       // Change controller method from test1 to test5.
-      $route->setDefault('_controller', '\Drupal\router_test\TestControllers::test5');
+      $route->setDefault('_content', '\Drupal\router_test\TestControllers::test5');
     }
   }
 
diff --git a/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/RouterTestServiceProvider.php b/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/RouterTestServiceProvider.php
index 9c1dbc3..641a055 100644
--- a/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/RouterTestServiceProvider.php
+++ b/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/RouterTestServiceProvider.php
@@ -21,6 +21,6 @@ class RouterTestServiceProvider implements ServiceProviderInterface {
   public function register(ContainerBuilder $container) {
     $container->register('router_test.subscriber', 'Drupal\router_test\RouteTestSubscriber')->addTag('event_subscriber');
     $container->register('access_check.router_test', 'Drupal\router_test\Access\TestAccessCheck')
-      ->addTag('access_check');
+      ->addTag('access_check', array('applies_to' => '_access_router_test'));
   }
 }
diff --git a/core/modules/system/tests/modules/router_test_directory/router_test.routing.yml b/core/modules/system/tests/modules/router_test_directory/router_test.routing.yml
index 59a53aa..7ad0607 100644
--- a/core/modules/system/tests/modules/router_test_directory/router_test.routing.yml
+++ b/core/modules/system/tests/modules/router_test_directory/router_test.routing.yml
@@ -30,7 +30,7 @@ router_test.4:
 router_test.6:
   path: '/router_test/test6'
   defaults:
-    _controller: '\Drupal\router_test\TestControllers::test1'
+    _content: '\Drupal\router_test\TestControllers::test1'
   requirements:
     _access: 'TRUE'
 
diff --git a/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php b/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php
index 44e2882..f27fe0d 100644
--- a/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php
+++ b/core/modules/system/tests/modules/session_test/lib/Drupal/session_test/EventSubscriber/SessionTestSubscriber.php
@@ -49,7 +49,7 @@ public function onKernelResponseSessionTest(FilterResponseEvent $event) {
       // https.php because form submissions would otherwise redirect to a
       // non-existent HTTPS site.
       if (!empty($is_https_mock)) {
-        $path = $base_insecure_url . '/' . $event->getTargetUrl();
+        $path = $base_insecure_url . '/' . $response->getTargetUrl();
         $response->setTargetUrl($path);
       }
     }
diff --git a/core/modules/system/tests/modules/session_test/session_test.routing.yml b/core/modules/system/tests/modules/session_test/session_test.routing.yml
index 1e7b127..17eafeb 100644
--- a/core/modules/system/tests/modules/session_test/session_test.routing.yml
+++ b/core/modules/system/tests/modules/session_test/session_test.routing.yml
@@ -2,7 +2,7 @@ session_test.get:
   path: '/session-test/get'
   defaults:
     _title: 'Session value'
-    _controller: '\Drupal\session_test\Controller\SessionTestController::get'
+    _content: '\Drupal\session_test\Controller\SessionTestController::get'
   requirements:
     _permission: 'access content'
 
@@ -10,7 +10,7 @@ session_test.id:
   path: '/session-test/id'
   defaults:
     _title: 'Session ID'
-    _controller: '\Drupal\session_test\Controller\SessionTestController::getId'
+    _content: '\Drupal\session_test\Controller\SessionTestController::getId'
   requirements:
     _permission: 'access content'
 
@@ -18,7 +18,7 @@ session_test.id_from_cookie:
   path: '/session-test/id-from-cookie'
   defaults:
     _title: 'Session ID from cookie'
-    _controller: '\Drupal\session_test\Controller\SessionTestController::getIdFromCookie'
+    _content: '\Drupal\session_test\Controller\SessionTestController::getIdFromCookie'
   requirements:
     _permission: 'access content'
 
@@ -26,7 +26,7 @@ session_test.set:
   path: '/session-test/set/{test_value}'
   defaults:
     _title: 'Set session value'
-    _controller: '\Drupal\session_test\Controller\SessionTestController::set'
+    _content: '\Drupal\session_test\Controller\SessionTestController::set'
   options:
     converters:
       test_value: '\s+'
@@ -37,7 +37,7 @@ session_test.no_set:
   path: '/session-test/no-set/{test_value}'
   defaults:
     _title: 'Set session value but do not save session'
-    _controller: '\Drupal\session_test\Controller\SessionTestController::noSet'
+    _content: '\Drupal\session_test\Controller\SessionTestController::noSet'
   options:
     converters:
       test_value: '\s+'
@@ -48,7 +48,7 @@ session_test.set_message:
   path: '/session-test/set-message'
   defaults:
     _title: 'Set message'
-    _controller: '\Drupal\session_test\Controller\SessionTestController::setMessage'
+    _content: '\Drupal\session_test\Controller\SessionTestController::setMessage'
   requirements:
     _permission: 'access content'
 
@@ -56,7 +56,7 @@ session_test.set_message_but_dont_save:
   path: '/session-test/set-message-but-dont-save'
   defaults:
     _title: 'Set message but do not save session'
-    _controller: '\Drupal\session_test\Controller\SessionTestController::setMessageButDontSave'
+    _content: '\Drupal\session_test\Controller\SessionTestController::setMessageButDontSave'
   requirements:
     _permission: 'access content'
 
@@ -64,7 +64,7 @@ session_test.set_not_started:
   path: '/session-test/set-not-started'
   defaults:
     _title: 'Set message when session is not started'
-    _controller: '\Drupal\session_test\Controller\SessionTestController::setNotStarted'
+    _content: '\Drupal\session_test\Controller\SessionTestController::setNotStarted'
   requirements:
     _permission: 'access content'
 
@@ -72,6 +72,6 @@ session_test.is_logged_in:
   path: '/session-test/is-logged-in'
   defaults:
     _title: 'Check if user is logged in'
-    _controller: '\Drupal\session_test\Controller\SessionTestController::isLoggedIn'
+    _content: '\Drupal\session_test\Controller\SessionTestController::isLoggedIn'
   requirements:
     _user_is_logged_in: 'TRUE'
diff --git a/core/modules/system/tests/modules/system_test/lib/Drupal/system_test/Controller/PageCacheAcceptHeaderController.php b/core/modules/system/tests/modules/system_test/lib/Drupal/system_test/Controller/PageCacheAcceptHeaderController.php
index cc3b231..43cded1 100644
--- a/core/modules/system/tests/modules/system_test/lib/Drupal/system_test/Controller/PageCacheAcceptHeaderController.php
+++ b/core/modules/system/tests/modules/system_test/lib/Drupal/system_test/Controller/PageCacheAcceptHeaderController.php
@@ -9,6 +9,7 @@
 
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Defines a controller to respond the page cache accept header test.
@@ -28,7 +29,7 @@ public function content(Request $request) {
       return new JsonResponse(array('content' => 'oh hai this is json'));
     }
     else {
-      return "<p>oh hai this is html.</p>";
+      return new Response("<p>oh hai this is html.</p>");
     }
   }
 }
diff --git a/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml b/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml
index 66f4f03..d9b016f 100644
--- a/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml
+++ b/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml
@@ -35,10 +35,3 @@ test_page_test.admin_render_title:
     _content: 'Drupal\test_page_test\Controller\Test::renderTitle'
   requirements:
     _access: 'TRUE'
-
-test_page_test.render_title_controller:
-  path: '/test-render-title-controller'
-  defaults:
-    _controller: 'Drupal\test_page_test\Controller\Test::renderTitle'
-  requirements:
-    _access: 'TRUE'
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module
index 52bff21..b8a528d 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.module
+++ b/core/modules/system/tests/modules/theme_test/theme_test.module
@@ -87,10 +87,7 @@ function theme_test_preprocess_html(&$variables) {
   $variables['html_attributes']['theme_test_html_attribute'] = 'theme test html attribute value';
   $variables['attributes']['theme_test_body_attribute'] = 'theme test body attribute value';
 
-  // Check that the page variable is not flattened yet.
-  if (is_array($variables['page'])) {
-    $variables['attributes']['theme_test_page_variable'] = 'Page variable is an array.';
-  }
+  $variables['attributes']['theme_test_page_variable'] = 'Page variable is an array.';
 }
 
 /**
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/views/argument_validator/TermName.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/views/argument_validator/TermName.php
index 56475f9..bbddc4b 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/views/argument_validator/TermName.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/views/argument_validator/TermName.php
@@ -9,7 +9,7 @@
 
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
-use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\views\Plugin\views\argument_validator\Entity;
 
 /**
@@ -33,7 +33,7 @@ class TermName extends Entity {
   /**
    * {@inheritdoc}
    */
-  public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityManager $entity_manager) {
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityManagerInterface $entity_manager) {
     parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_manager);
     // Not handling exploding term names.
     $this->multipleCapable = FALSE;
diff --git a/core/modules/taxonomy/taxonomy.local_tasks.yml b/core/modules/taxonomy/taxonomy.local_tasks.yml
index cdcb4ea..461d762 100644
--- a/core/modules/taxonomy/taxonomy.local_tasks.yml
+++ b/core/modules/taxonomy/taxonomy.local_tasks.yml
@@ -1,18 +1,18 @@
 taxonomy.term_page:
   title: 'View'
   route_name: taxonomy.term_page
-  tab_root_id: taxonomy.term_page
+  base_route: taxonomy.term_page
 
 taxonomy.term_edit:
   title: 'Edit'
   route_name: taxonomy.term_edit
-  tab_root_id: taxonomy.term_page
+  base_route: taxonomy.term_page
 
 taxonomy.overview_terms:
   title: 'List'
   route_name: taxonomy.overview_terms
-  tab_root_id: taxonomy.overview_terms
+  base_route: taxonomy.overview_terms
 taxonomy.vocabulary_edit:
   title: 'Edit'
   route_name: taxonomy.vocabulary_edit
-  tab_root_id: taxonomy.overview_terms
+  base_route: taxonomy.overview_terms
diff --git a/core/modules/taxonomy/taxonomy.views.inc b/core/modules/taxonomy/taxonomy.views.inc
index 4c4666b..d114050 100644
--- a/core/modules/taxonomy/taxonomy.views.inc
+++ b/core/modules/taxonomy/taxonomy.views.inc
@@ -437,21 +437,21 @@ function taxonomy_field_views_data(FieldInterface $field) {
 function taxonomy_field_views_data_views_data_alter(array &$data, FieldInterface $field) {
   $field_name = $field->getName();
   $entity_type = $field->entity_type;
-  $entity_info = entity_get_info($entity_type);
+  $entity_info = \Drupal::entityManager()->getDefinition($entity_type);
   $pseudo_field_name = 'reverse_' . $field_name . '_' . $entity_type;
 
   list($label) = field_views_field_label($entity_type, $field_name);
 
   $data['taxonomy_term_data'][$pseudo_field_name]['relationship'] = array(
-    'title' => t('@entity using @field', array('@entity' => $entity_info['label'], '@field' => $label)),
-    'help' => t('Relate each @entity with a @field set to the term.', array('@entity' => $entity_info['label'], '@field' => $label)),
+    'title' => t('@entity using @field', array('@entity' => $entity_info->getLabel(), '@field' => $label)),
+    'help' => t('Relate each @entity with a @field set to the term.', array('@entity' => $entity_info->getLabel(), '@field' => $label)),
     'id' => 'entity_reverse',
     'field_name' => $field_name,
     'entity_type' => $entity_type,
     'field table' => FieldableDatabaseStorageController::_fieldTableName($field),
     'field field' => $field_name . '_target_id',
-    'base' => $entity_info['base_table'],
-    'base field' => $entity_info['entity_keys']['id'],
+    'base' => $entity_info->getBaseTable(),
+    'base field' => $entity_info->getKey('id'),
     'label' => t('!field_name', array('!field_name' => $field_name)),
     'join_extra' => array(
       0 => array(
diff --git a/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php b/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php
index 9f986ab..f2af023 100644
--- a/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php
+++ b/core/modules/text/lib/Drupal/text/Tests/Formatter/TextPlainUnitTest.php
@@ -105,8 +105,8 @@ function setUp() {
    *   The created entity object.
    */
   protected function createEntity($values = array()) {
-    $info = entity_get_info($this->entity_type);
-    $bundle_key = $info['entity_keys']['bundle'];
+    $info = \Drupal::entityManager()->getDefinition($this->entity_type);
+    $bundle_key = $info->getKey('bundle');
     $entity = entity_create($this->entity_type, $values + array(
       $bundle_key => $this->bundle,
     ));
diff --git a/core/modules/tracker/lib/Drupal/tracker/Access/ViewOwnTrackerAccessCheck.php b/core/modules/tracker/lib/Drupal/tracker/Access/ViewOwnTrackerAccessCheck.php
index 11f42c1..a4c594e 100644
--- a/core/modules/tracker/lib/Drupal/tracker/Access/ViewOwnTrackerAccessCheck.php
+++ b/core/modules/tracker/lib/Drupal/tracker/Access/ViewOwnTrackerAccessCheck.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\tracker\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -15,14 +15,7 @@
 /**
  * Access check for user tracker routes.
  */
-class ViewOwnTrackerAccessCheck implements StaticAccessCheckInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function appliesTo() {
-    return array('_access_tracker_own_information');
-  }
+class ViewOwnTrackerAccessCheck implements AccessInterface {
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/tracker/tracker.local_tasks.yml b/core/modules/tracker/tracker.local_tasks.yml
index 8a0ab77..8092038 100644
--- a/core/modules/tracker/tracker.local_tasks.yml
+++ b/core/modules/tracker/tracker.local_tasks.yml
@@ -1,15 +1,15 @@
 tracker.page_tab:
   route_name: tracker.page
   title: 'Recent content'
-  tab_root_id: tracker.page_tab
+  base_route: tracker.page
 
 tracker.users_recent_tab:
   route_name: tracker.users_recent_content
   title: 'My recent content'
-  tab_root_id: tracker.page_tab
+  base_route: tracker.page
   class: '\Drupal\tracker\Plugin\Menu\UserTrackerTab'
 
 tracker.user_tab:
   route_name: tracker.user_tab
-  tab_root_id: user.view
+  base_route: user.view
   title: 'Track'
diff --git a/core/modules/tracker/tracker.services.yml b/core/modules/tracker/tracker.services.yml
index e657c1f..11e29e7 100644
--- a/core/modules/tracker/tracker.services.yml
+++ b/core/modules/tracker/tracker.services.yml
@@ -2,4 +2,4 @@ services:
   access_check.tracker.view_own:
     class: Drupal\tracker\Access\ViewOwnTrackerAccessCheck
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _access_tracker_own_information }
diff --git a/core/modules/update/lib/Drupal/update/Access/UpdateManagerAccessCheck.php b/core/modules/update/lib/Drupal/update/Access/UpdateManagerAccessCheck.php
index 4c805cd..89bbb1d 100644
--- a/core/modules/update/lib/Drupal/update/Access/UpdateManagerAccessCheck.php
+++ b/core/modules/update/lib/Drupal/update/Access/UpdateManagerAccessCheck.php
@@ -8,7 +8,7 @@
 namespace Drupal\update\Access;
 
 use Drupal\Component\Utility\Settings;
-use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -16,7 +16,7 @@
 /**
  * Determines whether allow authorized operations is set.
  */
-class UpdateManagerAccessCheck implements StaticAccessCheckInterface {
+class UpdateManagerAccessCheck implements AccessInterface {
 
   /**
    * Settings Service.
@@ -28,8 +28,8 @@ class UpdateManagerAccessCheck implements StaticAccessCheckInterface {
   /**
    * Constructs a UpdateManagerAccessCheck object.
    *
-   * @param \Drupal\update\updateManager $update_manager
-   *   update Manager Service.
+   * @param \Drupal\Component\Utility\Settings $settings
+   *   The read-only settings container.
    */
   public function __construct(Settings $settings) {
     $this->settings = $settings;
@@ -38,13 +38,6 @@ public function __construct(Settings $settings) {
   /**
    * {@inheritdoc}
    */
-  public function appliesTo() {
-    return array('_access_update_manager');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access(Route $route, Request $request, AccountInterface $account) {
     return $this->settings->get('allow_authorize_operations', TRUE) ? static::ALLOW : static::DENY;
   }
diff --git a/core/modules/update/update.local_tasks.yml b/core/modules/update/update.local_tasks.yml
index 1281a27..e817f3e 100644
--- a/core/modules/update/update.local_tasks.yml
+++ b/core/modules/update/update.local_tasks.yml
@@ -1,27 +1,26 @@
 update.status:
   route_name: update.status
-  tab_root_id: system.admin_reports
+  base_route: system.admin_reports
   title: List
 update.settings:
   route_name: update.settings
-  tab_root_id: system.admin_reports
-  tab_parent_id: update.status
+  parent_id: update.status
   title: Settings
   weight: 50
 update.report_install:
   route_name: update.report_install
-  tab_root_id: system.admin_reports
+  base_route: system.admin_reports
   title: Update
   weight: 10
 
 update.module_install:
   route_name: update.module_install
-  tab_root_id: system.modules_list
+  base_route: system.modules_list
   title: Update
   weight: 10
 
 update.theme_install:
   route_name: update.theme_install
-  tab_root_id: system.themes_page
+  base_route: system.themes_page
   title: Update
   weight: 10
diff --git a/core/modules/update/update.services.yml b/core/modules/update/update.services.yml
index 0ffafc2..7008182 100644
--- a/core/modules/update/update.services.yml
+++ b/core/modules/update/update.services.yml
@@ -3,4 +3,4 @@ services:
     class: Drupal\update\Access\UpdateManagerAccessCheck
     arguments: ['@settings']
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _access_update_manager }
diff --git a/core/modules/user/config/views.view.who_s_new.yml b/core/modules/user/config/views.view.who_s_new.yml
new file mode 100644
index 0000000..3ca47b3
--- /dev/null
+++ b/core/modules/user/config/views.view.who_s_new.yml
@@ -0,0 +1,166 @@
+base_field: uid
+base_table: users
+core: 8.x
+description: 'Shows a list of the newest user accounts on the site.'
+status: '1'
+display:
+  block_1:
+    display_plugin: block
+    id: block_1
+    display_title: 'Who''s new'
+    position: '1'
+    display_options:
+      display_description: 'A list of new users'
+      block_description: 'Who''s new'
+      block_category: 'User'
+  default:
+    display_plugin: default
+    id: default
+    display_title: Master
+    position: '1'
+    display_options:
+      access:
+        type: perm
+        options:
+          perm: 'access content'
+        perm: 'access user profiles'
+      cache:
+        type: none
+        options: {  }
+      query:
+        type: views_query
+        options:
+          disable_sql_rewrite: '0'
+          distinct: '0'
+          slave: '0'
+          query_comment: ''
+          query_tags: {  }
+      exposed_form:
+        type: basic
+        options:
+          submit_button: Apply
+          reset_button: '0'
+          reset_button_label: Reset
+          exposed_sorts_label: 'Sort by'
+          expose_sort_order: '1'
+          sort_asc_label: Asc
+          sort_desc_label: Desc
+      pager:
+        type: some
+        options:
+          items_per_page: '5'
+          offset: '0'
+      style:
+        type: html_list
+      row:
+        type: fields
+      fields:
+        name:
+          id: name
+          table: users
+          field: name
+          label: ''
+          alter:
+            alter_text: '0'
+            make_link: '0'
+            absolute: '0'
+            trim: '0'
+            word_boundary: '0'
+            ellipsis: '0'
+            strip_tags: '0'
+            html: '0'
+          hide_empty: '0'
+          empty_zero: '0'
+          link_to_user: '1'
+          overwrite_anonymous: '0'
+          relationship: none
+          group_type: group
+          admin_label: ''
+          exclude: '0'
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: '1'
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: '1'
+          empty: ''
+          hide_alter_empty: '1'
+          anonymous_text: ''
+          format_username: '1'
+      filters:
+        status:
+          value: '1'
+          table: users
+          field: status
+          id: status
+          expose:
+            operator: '0'
+          group: '1'
+        access:
+          id: access
+          table: users
+          field: access
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '>'
+          value:
+            min: ''
+            max: ''
+            value: '1970-01-01'
+            type: date
+          group: '1'
+          exposed: '0'
+          expose:
+            operator_id: '0'
+            label: ''
+            description: ''
+            use_operator: '0'
+            operator: ''
+            identifier: ''
+            required: '0'
+            remember: '0'
+            multiple: '0'
+            remember_roles:
+              authenticated: authenticated
+          is_grouped: '0'
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: '1'
+            widget: select
+            multiple: '0'
+            remember: '0'
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          plugin_id: date
+      sorts:
+        created:
+          id: created
+          table: users
+          field: created
+          relationship: none
+          group_type: group
+          admin_label: ''
+          order: DESC
+          exposed: '0'
+          expose:
+            label: ''
+          granularity: second
+          plugin_id: date
+      title: 'Who''s new'
+      header: {  }
+      footer: {  }
+      empty: {  }
+      relationships: {  }
+      arguments: {  }
+label: 'Who''s new'
+module: views
+id: who_s_new
+tag: 'default'
+uuid: 8b2c05e3-046b-447f-922b-43a832220667
+langcode: en
diff --git a/core/modules/user/lib/Drupal/user/Access/LoginStatusCheck.php b/core/modules/user/lib/Drupal/user/Access/LoginStatusCheck.php
index 547057b..af52c15 100644
--- a/core/modules/user/lib/Drupal/user/Access/LoginStatusCheck.php
+++ b/core/modules/user/lib/Drupal/user/Access/LoginStatusCheck.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\user\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -15,14 +15,7 @@
 /**
  * Determines access to routes based on login status of current user.
  */
-class LoginStatusCheck implements StaticAccessCheckInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function appliesTo() {
-    return array('_user_is_logged_in');
-  }
+class LoginStatusCheck implements AccessInterface {
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/user/lib/Drupal/user/Access/PermissionAccessCheck.php b/core/modules/user/lib/Drupal/user/Access/PermissionAccessCheck.php
index b9d084e..a159b1c 100644
--- a/core/modules/user/lib/Drupal/user/Access/PermissionAccessCheck.php
+++ b/core/modules/user/lib/Drupal/user/Access/PermissionAccessCheck.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\user\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -15,14 +15,7 @@
 /**
  * Determines access to routes based on permissions defined via hook_permission().
  */
-class PermissionAccessCheck implements StaticAccessCheckInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function appliesTo() {
-    return array('_permission');
-  }
+class PermissionAccessCheck implements AccessInterface {
 
   /**
    * Implements AccessCheckInterface::access().
diff --git a/core/modules/user/lib/Drupal/user/Access/RegisterAccessCheck.php b/core/modules/user/lib/Drupal/user/Access/RegisterAccessCheck.php
index 6ba064e..e234afb 100644
--- a/core/modules/user/lib/Drupal/user/Access/RegisterAccessCheck.php
+++ b/core/modules/user/lib/Drupal/user/Access/RegisterAccessCheck.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\user\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
@@ -15,14 +15,7 @@
 /**
  * Access check for user registration routes.
  */
-class RegisterAccessCheck implements StaticAccessCheckInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function appliesTo() {
-    return array('_access_user_register');
-  }
+class RegisterAccessCheck implements AccessInterface {
 
   /**
    * Implements AccessCheckInterface::access().
diff --git a/core/modules/user/lib/Drupal/user/Access/RoleAccessCheck.php b/core/modules/user/lib/Drupal/user/Access/RoleAccessCheck.php
index 0253a42..e3ace16 100644
--- a/core/modules/user/lib/Drupal/user/Access/RoleAccessCheck.php
+++ b/core/modules/user/lib/Drupal/user/Access/RoleAccessCheck.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\user\Access;
 
-use Drupal\Core\Access\StaticAccessCheckInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
@@ -19,14 +19,7 @@
  * single role, users with that role with have access. If you specify multiple
  * ones you can conjunct them with AND by using a "+" and with OR by using ",".
  */
-class RoleAccessCheck implements StaticAccessCheckInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function appliesTo() {
-    return array('_role');
-  }
+class RoleAccessCheck implements AccessInterface {
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/user/lib/Drupal/user/Controller/UserController.php b/core/modules/user/lib/Drupal/user/Controller/UserController.php
index e3d7431..b7196bf 100644
--- a/core/modules/user/lib/Drupal/user/Controller/UserController.php
+++ b/core/modules/user/lib/Drupal/user/Controller/UserController.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\user\UserInterface;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Controller routines for user routes.
@@ -22,17 +23,24 @@ class UserController extends ControllerBase {
    * Displays user profile if user is logged in, or login form for anonymous
    * users.
    *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   *
    * @return \Symfony\Component\HttpFoundation\RedirectResponse|array
    *   Returns either a redirect to the user page or the render
    *   array of the login form.
    */
-  public function userPage() {
+  public function userPage(Request $request) {
     $user = $this->currentUser();
     if ($user->id()) {
       $response = $this->redirect('user.view', array('user' => $user->id()));
     }
     else {
-      $response = drupal_get_form('Drupal\user\Form\UserLoginForm');
+      // Sets the proper request.
+      // @todo Remove when the request object is synchronized.
+      $form_builder = \Drupal::formBuilder();
+      $form_builder->setRequest($request);
+      $response = $form_builder->getForm('Drupal\user\Form\UserLoginForm');
     }
     return $response;
   }
diff --git a/core/modules/user/lib/Drupal/user/Controller/UserListController.php b/core/modules/user/lib/Drupal/user/Controller/UserListController.php
index 7f8da1d..4d2a406 100644
--- a/core/modules/user/lib/Drupal/user/Controller/UserListController.php
+++ b/core/modules/user/lib/Drupal/user/Controller/UserListController.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityListController;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -32,10 +33,8 @@ class UserListController extends EntityListController implements EntityControlle
   /**
    * Constructs a new UserListController object.
    *
-   * @param string $entity_type
-   *   The type of entity to be listed.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage
    *   The entity storage controller class.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
@@ -43,19 +42,18 @@ class UserListController extends EntityListController implements EntityControlle
    * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
    *   The entity query factory.
    */
-  public function __construct($entity_type, array $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, QueryFactory $query_factory) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct(EntityTypeInterface $entity_info, EntityStorageControllerInterface $storage, ModuleHandlerInterface $module_handler, QueryFactory $query_factory) {
+    parent::__construct($entity_info, $storage, $module_handler);
     $this->queryFactory = $query_factory;
   }
 
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
-      $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getStorageController($entity_info->id()),
       $container->get('module_handler'),
       $container->get('entity.query')
     );
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Block/UserNewBlock.php b/core/modules/user/lib/Drupal/user/Plugin/Block/UserNewBlock.php
deleted file mode 100644
index b6af908..0000000
--- a/core/modules/user/lib/Drupal/user/Plugin/Block/UserNewBlock.php
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\user\Plugin\Block\UserNewBlock.
- */
-
-namespace Drupal\user\Plugin\Block;
-
-use Drupal\block\BlockBase;
-use Drupal\Core\Session\AccountInterface;
-
-/**
- * Provides a "Who's new" block.
- *
- * @Block(
- *   id = "user_new_block",
- *   admin_label = @Translation("Who's new"),
- *   category = @Translation("Lists (Views)")
- * )
- */
-class UserNewBlock extends BlockBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function defaultConfiguration() {
-    return array(
-      'properties' => array(
-        'administrative' => TRUE
-      ),
-      'whois_new_count' => 5
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function access(AccountInterface $account) {
-    return $account->hasPermission('access content');
-  }
-
-  /**
-   * Overrides \Drupal\block\BlockBase::blockForm().
-   */
-  public function blockForm($form, &$form_state) {
-    $form['user_block_whois_new_count'] = array(
-      '#type' => 'select',
-      '#title' => t('Number of users to display'),
-      '#default_value' => $this->configuration['whois_new_count'],
-      '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
-    );
-    return $form;
-  }
-
-  /**
-   * Overrides \Drupal\block\BlockBase::blockSubmit().
-   */
-  public function blockSubmit($form, &$form_state) {
-    $this->configuration['whois_new_count'] = $form_state['values']['user_block_whois_new_count'];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function build() {
-    // Retrieve a list of new users who have accessed the site successfully.
-    $uids = db_query_range('SELECT uid FROM {users} WHERE status <> 0 AND access <> 0 ORDER BY created DESC', 0, $this->configuration['whois_new_count'])->fetchCol();
-    $build = array(
-      '#theme' => 'item_list__user__new',
-      '#items' => array(),
-    );
-    foreach (user_load_multiple($uids) as $account) {
-      $username = array(
-        '#theme' => 'username',
-        '#account' => $account,
-      );
-      $build['#items'][] = drupal_render($username);
-    }
-    return $build;
-  }
-
-}
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserAdminTest.php b/core/modules/user/lib/Drupal/user/Tests/UserAdminTest.php
index 1f28492..fb58bc1 100644
--- a/core/modules/user/lib/Drupal/user/Tests/UserAdminTest.php
+++ b/core/modules/user/lib/Drupal/user/Tests/UserAdminTest.php
@@ -159,7 +159,8 @@ function testNotificationEmailAddress() {
     // Notification E-mail address.
     $user_mail = $this->drupalGetMails(array(
       'to' => $edit['mail'],
-      'from' => $notify_address,
+      'from' => $server_address,
+      'reply-to' => $notify_address,
       'subject' => $subject,
     ));
     $this->assertTrue(count($user_mail), 'New user mail to user is sent from configured Notification E-mail address');
diff --git a/core/modules/user/lib/Drupal/user/Tests/Views/BulkFormTest.php b/core/modules/user/lib/Drupal/user/Tests/Views/BulkFormTest.php
index 2dd9f08..e6cb0c4 100644
--- a/core/modules/user/lib/Drupal/user/Tests/Views/BulkFormTest.php
+++ b/core/modules/user/lib/Drupal/user/Tests/Views/BulkFormTest.php
@@ -40,8 +40,6 @@ public function testBulkForm() {
       'action' => 'user_block_user_action',
     );
     $this->drupalPostForm('test-user-bulk-form', $edit, t('Apply'));
-    // @todo Validation errors are only shown on page refresh.
-    $this->drupalGet('test-user-bulk-form');
     $this->assertText(t('No users selected.'));
 
     // Assign a role to a user.
diff --git a/core/modules/user/lib/Drupal/user/Theme/AdminNegotiator.php b/core/modules/user/lib/Drupal/user/Theme/AdminNegotiator.php
index a12e157..faaeb40 100644
--- a/core/modules/user/lib/Drupal/user/Theme/AdminNegotiator.php
+++ b/core/modules/user/lib/Drupal/user/Theme/AdminNegotiator.php
@@ -8,7 +8,7 @@
 namespace Drupal\user\Theme;
 
 use Drupal\Core\Config\ConfigFactory;
-use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Theme\ThemeNegotiatorInterface;
 use Symfony\Component\HttpFoundation\Request;
@@ -35,7 +35,7 @@ class AdminNegotiator implements ThemeNegotiatorInterface {
   /**
    * The entity manager.
    *
-   * @var \Drupal\Core\Entity\EntityManager
+   * @var \Drupal\Core\Entity\EntityManagerInterface
    */
   protected $entityManager;
 
@@ -46,10 +46,10 @@ class AdminNegotiator implements ThemeNegotiatorInterface {
    *   The current user.
    * @param \Drupal\Core\Config\ConfigFactory $config_factory
    *   The config factory.
-   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
    */
-  public function __construct(AccountInterface $user, ConfigFactory $config_factory, EntityManager $entity_manager) {
+  public function __construct(AccountInterface $user, ConfigFactory $config_factory, EntityManagerInterface $entity_manager) {
     $this->user = $user;
     $this->configFactory = $config_factory;
     $this->entityManager = $entity_manager;
diff --git a/core/modules/user/lib/Drupal/user/UserStorageController.php b/core/modules/user/lib/Drupal/user/UserStorageController.php
index 23b2d27..69cfdba 100644
--- a/core/modules/user/lib/Drupal/user/UserStorageController.php
+++ b/core/modules/user/lib/Drupal/user/UserStorageController.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Uuid\UuidInterface;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Password\PasswordInterface;
 use Drupal\Core\Database\Connection;
 use Drupal\field\FieldInfo;
@@ -41,10 +42,8 @@ class UserStorageController extends FieldableDatabaseStorageController implement
   /**
    * Constructs a new UserStorageController object.
    *
-   * @param string $entity_type
-   *  The entity type for which the instance is created.
-   * @param array $entity_info
-   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
+   *   The entity info for the entity type.
    * @param \Drupal\Core\Database\Connection $database
    *   The database connection to be used.
    * @param \Drupal\field\FieldInfo $field_info
@@ -56,8 +55,8 @@ class UserStorageController extends FieldableDatabaseStorageController implement
    * @param \Drupal\user\UserDataInterface $user_data
    *   The user data service.
    */
-  public function __construct($entity_type, $entity_info, Connection $database, FieldInfo $field_info, UuidInterface $uuid_service, PasswordInterface $password, UserDataInterface $user_data) {
-    parent::__construct($entity_type, $entity_info, $database, $field_info, $uuid_service);
+  public function __construct(EntityTypeInterface $entity_info, Connection $database, FieldInfo $field_info, UuidInterface $uuid_service, PasswordInterface $password, UserDataInterface $user_data) {
+    parent::__construct($entity_info, $database, $field_info, $uuid_service);
 
     $this->password = $password;
     $this->userData = $user_data;
@@ -66,9 +65,8 @@ public function __construct($entity_type, $entity_info, Connection $database, Fi
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
       $entity_info,
       $container->get('database'),
       $container->get('field.info'),
diff --git a/core/modules/user/tests/Drupal/user/Tests/Views/Argument/RolesRidTest.php b/core/modules/user/tests/Drupal/user/Tests/Views/Argument/RolesRidTest.php
index a546d73..054d25a 100644
--- a/core/modules/user/tests/Drupal/user/Tests/Views/Argument/RolesRidTest.php
+++ b/core/modules/user/tests/Drupal/user/Tests/Views/Argument/RolesRidTest.php
@@ -20,20 +20,6 @@
  */
 class RolesRidTest extends UnitTestCase {
 
-  /**
-   * Entity info used by the test.
-   *
-   * @var array
-   */
-  public static $entityInfo = array(
-    'entity_keys' => array(
-      'id' => 'id',
-      'label' => 'label',
-    ),
-    'config_prefix' => 'user.role',
-    'class' => 'Drupal\user\Entity\Role',
-  );
-
   public static function getInfo() {
     return array(
       'name' => 'User: Roles Rid Argument',
@@ -67,12 +53,17 @@ public function testTitleQuery() {
         array(array('test_rid_1', 'test_rid_2'), array('test_rid_1' => $role1, 'test_rid_2' => $role2)),
       )));
 
-    $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
+    $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+    $entity_type->expects($this->any())
+      ->method('getKey')
+      ->with('label')
+      ->will($this->returnValue('label'));
 
+    $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
     $entity_manager->expects($this->any())
       ->method('getDefinition')
       ->with($this->equalTo('user_role'))
-      ->will($this->returnValue(static::$entityInfo));
+      ->will($this->returnValue($entity_type));
 
     $entity_manager
       ->expects($this->once())
diff --git a/core/modules/user/user.local_tasks.yml b/core/modules/user/user.local_tasks.yml
index a9cff5b..88957e4 100644
--- a/core/modules/user/user.local_tasks.yml
+++ b/core/modules/user/user.local_tasks.yml
@@ -2,54 +2,53 @@
 user.role_edit:
   title: 'Edit'
   route_name: user.role_edit
-  tab_root_id: user.role_edit
+  base_route: user.role_edit
 
 user.account_settings_tab:
   route_name: user.account_settings
   title: 'Settings'
-  tab_root_id: user.account_settings_tab
+  base_route: user.account_settings
 
 user.page:
   route_name: user.page
-  tab_root_id: user.page
+  base_route: user.page
   title: 'Log in'
   weight: -10
 user.register:
   route_name: user.register
-  tab_root_id: user.page
+  base_route: user.page
   title: 'Create new account'
 user.pass:
   route_name: user.pass
-  tab_root_id: user.page
+  base_route: user.page
   title: 'Request new password'
 # Other authentication methods may add pages below user/login/.
 user.login:
   route_name: user.login
-  tab_root_id: user.page
-  tab_parent_id: user.page
+  parent_id: user.page
   title: 'Username and password'
 
 user.view:
   route_name: user.view
-  tab_root_id: user.view
+  base_route: user.view
   title: View
 user.edit:
   route_name: user.edit
-  tab_root_id: user.view
+  base_route: user.view
   title: Edit
 
 user.admin_account:
   title: List
   route_name: user.admin_account
-  tab_root_id: user.admin_account
+  base_route: user.admin_account
 
 user.admin_permissions:
   title: Permissions
   route_name: user.admin_permissions
-  tab_root_id: user.admin_account
+  base_route: user.admin_account
 
 user.role_list:
   title: 'Roles'
   route_name: user.role_list
-  tab_root_id: user.admin_account
+  base_route: user.admin_account
   weight: 10
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index cfb48f8..b5604a1 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -552,9 +552,6 @@ function user_preprocess_block(&$variables) {
       case 'user_login_block':
         $variables['attributes']['role'] = 'form';
         break;
-      case 'user_new_block':
-        $variables['attributes']['role'] = 'complementary';
-        break;
     }
   }
 }
diff --git a/core/modules/user/user.services.yml b/core/modules/user/user.services.yml
index d255dfd..72629e0 100644
--- a/core/modules/user/user.services.yml
+++ b/core/modules/user/user.services.yml
@@ -2,19 +2,19 @@ services:
   access_check.permission:
     class: Drupal\user\Access\PermissionAccessCheck
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _permission }
   access_check.user.register:
     class: Drupal\user\Access\RegisterAccessCheck
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _access_user_register }
   access_check.user.role:
     class: Drupal\user\Access\RoleAccessCheck
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _role }
   access_check.user.login_status:
     class: Drupal\user\Access\LoginStatusCheck
     tags:
-      - { name: access_check }
+      - { name: access_check, applies_to: _user_is_logged_in }
   user.data:
     class: Drupal\user\UserData
     arguments: ['@database']
diff --git a/core/modules/views/lib/Drupal/views/EventSubscriber/RouteSubscriber.php b/core/modules/views/lib/Drupal/views/EventSubscriber/RouteSubscriber.php
index 14da989..9540364 100644
--- a/core/modules/views/lib/Drupal/views/EventSubscriber/RouteSubscriber.php
+++ b/core/modules/views/lib/Drupal/views/EventSubscriber/RouteSubscriber.php
@@ -8,13 +8,16 @@
 namespace Drupal\views\EventSubscriber;
 
 use Drupal\Component\Utility\MapArray;
-use Drupal\Core\DestructableInterface;
+use Drupal\Core\Page\HtmlPage;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\KeyValueStore\StateInterface;
 use Drupal\Core\Routing\RouteSubscriberBase;
+use Drupal\Core\Routing\RoutingEvents;
 use Drupal\views\Plugin\views\display\DisplayRouterInterface;
 use Drupal\views\ViewExecutable;
 use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
 
 /**
  * Builds up the routes of all views.
@@ -23,9 +26,11 @@
  * routes are overridden by views. This information is used to determine which
  * views have to be added by views in the dynamic event.
  *
+ * Additional to adding routes it also changes the htmlpage response code.
+ *
  * @see \Drupal\views\Plugin\views\display\PathPluginBase
  */
-class RouteSubscriber extends RouteSubscriberBase implements DestructableInterface {
+class RouteSubscriber extends RouteSubscriberBase {
 
   /**
    * Stores a list of view,display IDs which haven't be used in the alter event.
@@ -76,6 +81,16 @@ public function reset() {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events = parent::getSubscribedEvents();
+    $events[KernelEvents::VIEW][] = array('onHtmlPage', 75);
+    $events[RoutingEvents::FINISHED] = array('routeRebuildFinished');
+    return $events;
+  }
+
+  /**
    * Gets all the views and display IDs using a route.
    */
   protected function getViewsDisplayIDsWithRoute() {
@@ -95,6 +110,23 @@ protected function getViewsDisplayIDsWithRoute() {
   }
 
   /**
+   * Sets the proper response code coming from the http status area handler.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event
+   *   The Event to process.
+   *
+   * @see \Drupal\views\Plugin\views\area\HTTPStatusCode
+   */
+  public function onHtmlPage(GetResponseForControllerResultEvent $event) {
+    $page = $event->getControllerResult();
+    if ($page instanceof HtmlPage) {
+      if (($request = $event->getRequest()) && $request->attributes->has('view_id')) {
+        $page->setStatusCode($request->attributes->get('_http_statuscode', 200));
+      };
+    }
+  }
+
+  /**
    * Returns a set of route objects.
    *
    * @return \Symfony\Component\Routing\RouteCollection
@@ -148,7 +180,7 @@ protected function alterRoutes(RouteCollection $collection, $provider) {
   /**
    * {@inheritdoc}
    */
-  public function destruct() {
+  public function routeRebuildFinished() {
     $this->state->set('views.view_route_names', $this->viewRouteNames);
   }
 
diff --git a/core/modules/views/lib/Drupal/views/ExposedFormCache.php b/core/modules/views/lib/Drupal/views/ExposedFormCache.php
new file mode 100644
index 0000000..30b4a60
--- /dev/null
+++ b/core/modules/views/lib/Drupal/views/ExposedFormCache.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\ExposedFormCache.
+ */
+
+namespace Drupal\views;
+
+/**
+ * Caches exposed forms, as they are heavy to generate.
+ *
+ * @see \Drupal\views\Form\ViewsExposedForm
+ */
+class ExposedFormCache {
+
+  /**
+   * Stores the exposed form data.
+   *
+   * @var array
+   */
+  protected $cache = array();
+
+  /**
+   * Save the Views exposed form for later use.
+   *
+   * @param string $view_id
+   *   The views ID.
+   * @param string $display_id
+   *   The current view display name.
+   * @param array $form_output
+   *   The form structure. Only needed when inserting the value.
+   */
+  public function setForm($view_id, $display_id, array $form_output) {
+    // Save the form output.
+    $views_exposed[$view_id][$display_id] = $form_output;
+  }
+
+  /**
+   * Retrieves the views exposed form from cache.
+   *
+   * @param string $view_id
+   *   The views ID.
+   * @param string $display_id
+   *   The current view display name.
+   *
+   * @return array|bool
+   *   The form structure, if any, otherwise FALSE.
+   */
+  public function getForm($view_id, $display_id) {
+    // Return the form output, if any.
+    if (empty($this->cache[$view_id][$display_id])) {
+      return FALSE;
+    }
+    else {
+      return $this->cache[$view_id][$display_id];
+    }
+  }
+
+  /**
+   * Rests the form cache.
+   */
+  public function reset() {
+    $this->cache = array();
+  }
+
+}
diff --git a/core/modules/views/lib/Drupal/views/Form/ViewsExposedForm.php b/core/modules/views/lib/Drupal/views/Form/ViewsExposedForm.php
new file mode 100644
index 0000000..ae3da03
--- /dev/null
+++ b/core/modules/views/lib/Drupal/views/Form/ViewsExposedForm.php
@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\Form\ViewsExposedForm.
+ */
+
+namespace Drupal\views\Form;
+
+use Drupal\Component\Utility\String;
+use Drupal\Core\Form\FormBase;
+use Drupal\views\ExposedFormCache;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides the views exposed form.
+ */
+class ViewsExposedForm extends FormBase {
+
+  /**
+   * The exposed form cache.
+   *
+   * @var \Drupal\views\ExposedFormCache
+   */
+  protected $exposedFormCache;
+
+  /**
+   * Constructs a new ViewsExposedForm
+   *
+   * @param \Drupal\views\ExposedFormCache $exposed_form_cache
+   *   The exposed form cache.
+   */
+  public function __construct(ExposedFormCache $exposed_form_cache) {
+    $this->exposedFormCache = $exposed_form_cache;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('views.exposed_form_cache'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'views_exposed_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state) {
+    // Don't show the form when batch operations are in progress.
+    if ($batch = batch_get() && isset($batch['current_set'])) {
+      return array(
+        // Set the theme callback to be nothing to avoid errors in template_preprocess_views_exposed_form().
+        '#theme' => '',
+      );
+    }
+
+    // Make sure that we validate because this form might be submitted
+    // multiple times per page.
+    $form_state['must_validate'] = TRUE;
+    /** @var \Drupal\views\ViewExecutable $view */
+    $view = $form_state['view'];
+    $display = &$form_state['display'];
+
+    $form_state['input'] = $view->getExposedInput();
+
+    // Let form plugins know this is for exposed widgets.
+    $form_state['exposed'] = TRUE;
+    // Check if the form was already created
+    if ($cache = $this->exposedFormCache->getForm($view->storage->id(), $view->current_display)) {
+      return $cache;
+    }
+
+    $form['#info'] = array();
+
+    // Go through each handler and let it generate its exposed widget.
+    foreach ($view->display_handler->handlers as $type => $value) {
+      /** @var \Drupal\views\Plugin\views\HandlerBase $handler */
+      foreach ($view->$type as $id => $handler) {
+        if ($handler->canExpose() && $handler->isExposed()) {
+          // Grouped exposed filters have their own forms.
+          // Instead of render the standard exposed form, a new Select or
+          // Radio form field is rendered with the available groups.
+          // When an user choose an option the selected value is split
+          // into the operator and value that the item represents.
+          if ($handler->isAGroup()) {
+            $handler->groupForm($form, $form_state);
+            $id = $handler->options['group_info']['identifier'];
+          }
+          else {
+            $handler->buildExposedForm($form, $form_state);
+          }
+          if ($info = $handler->exposedInfo()) {
+            $form['#info']["$type-$id"] = $info;
+          }
+        }
+      }
+    }
+
+    $form['actions'] = array(
+      '#type' => 'actions'
+    );
+    $form['actions']['submit'] = array(
+      // Prevent from showing up in \Drupal::request()->query.
+      '#name' => '',
+      '#type' => 'submit',
+      '#value' => $this->t('Apply'),
+      '#id' => drupal_html_id('edit-submit-' . $view->storage->id()),
+    );
+
+    $form['#action'] = url($view->display_handler->getUrl());
+    $form['#theme'] = $view->buildThemeFunctions('views_exposed_form');
+    $form['#id'] = drupal_clean_css_identifier('views_exposed_form-' . String::checkPlain($view->storage->id()) . '-' . String::checkPlain($display['id']));
+    // $form['#attributes']['class'] = array('views-exposed-form');
+
+    /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginBase $exposed_form_plugin */
+    $exposed_form_plugin = $form_state['exposed_form_plugin'];
+    $exposed_form_plugin->exposedFormAlter($form, $form_state);
+
+    // Save the form.
+    $this->exposedFormCache->setForm($view->storage->id(), $view->current_display, $form);
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, array &$form_state) {
+    foreach (array('field', 'filter') as $type) {
+      /** @var \Drupal\views\Plugin\views\HandlerBase[] $handlers */
+      $handlers = &$form_state['view']->$type;
+      foreach ($handlers as $key => $handler) {
+        $handlers[$key]->validateExposed($form, $form_state);
+      }
+    }
+    /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginBase $exposed_form_plugin */
+    $exposed_form_plugin = $form_state['exposed_form_plugin'];
+    $exposed_form_plugin->exposedFormValidate($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    foreach (array('field', 'filter') as $type) {
+      /** @var \Drupal\views\Plugin\views\HandlerBase[] $handlers */
+      $handlers = &$form_state['view']->$type;
+      foreach ($handlers as $key => $info) {
+        $handlers[$key]->submitExposed($form, $form_state);
+      }
+    }
+    $form_state['view']->exposed_data = $form_state['values'];
+    $form_state['view']->exposed_raw_input = array();
+
+    $exclude = array('submit', 'form_build_id', 'form_id', 'form_token', 'exposed_form_plugin', '', 'reset');
+    /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginBase $exposed_form_plugin */
+    $exposed_form_plugin = $form_state['exposed_form_plugin'];
+    $exposed_form_plugin->exposedFormSubmit($form, $form_state, $exclude);
+
+    foreach ($form_state['values'] as $key => $value) {
+      if (!in_array($key, $exclude)) {
+        $form_state['view']->exposed_raw_input[$key] = $value;
+      }
+    }
+  }
+
+}
diff --git a/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlock.php b/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlock.php
index 4d521a9..0804b55 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlock.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/Block/ViewsBlock.php
@@ -56,6 +56,11 @@ public function defaultConfiguration() {
       $settings += $this->view->display_handler->blockSettings($settings);
     }
 
+    // Set custom cache settings.
+    if (isset($this->pluginDefinition['cache'])) {
+      $settings['cache'] = $this->pluginDefinition['cache'];
+    }
+
     return $settings;
   }
 
diff --git a/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsEntityArgumentValidator.php b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsEntityArgumentValidator.php
index 5d77a9b..b5f026d 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsEntityArgumentValidator.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsEntityArgumentValidator.php
@@ -9,7 +9,7 @@
 
 use Drupal\Component\Plugin\Derivative\DerivativeBase;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeInterface;
-use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -31,7 +31,7 @@ class ViewsEntityArgumentValidator extends DerivativeBase implements ContainerDe
   /**
    * The entity manager.
    *
-   * @var \Drupal\Core\Entity\EntityManager
+   * @var \Drupal\Core\Entity\EntityManagerInterface
    */
   protected $entityManager;
 
@@ -54,12 +54,12 @@ class ViewsEntityArgumentValidator extends DerivativeBase implements ContainerDe
    *
    * @param string $base_plugin_id
    *   The base plugin ID.
-   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
    * @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
    *   The string translation.
    */
-  public function __construct($base_plugin_id, EntityManager $entity_manager, TranslationInterface $translation_manager) {
+  public function __construct($base_plugin_id, EntityManagerInterface $entity_manager, TranslationInterface $translation_manager) {
     $this->basePluginId = $base_plugin_id;
     $this->entityManager = $entity_manager;
     $this->translationManager = $translation_manager;
@@ -86,8 +86,8 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
       $this->derivatives[$entity_type] = array(
         'id' => 'entity:' . $entity_type,
         'provider' => 'views',
-        'title' => $entity_info['label'],
-        'help' => $this->t('Validate @label', array('@label' => $entity_info['label'])),
+        'title' => $entity_info->getLabel(),
+        'help' => $this->t('Validate @label', array('@label' => $entity_info->getLabel())),
         'entity_type' => $entity_type,
         'class' => $base_plugin_definition['class'],
       );
diff --git a/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsEntityRow.php b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsEntityRow.php
index 7f11cae..5058364 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsEntityRow.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsEntityRow.php
@@ -93,13 +93,13 @@ public function getDerivativeDefinition($derivative_id, array $base_plugin_defin
   public function getDerivativeDefinitions(array $base_plugin_definition) {
     foreach ($this->entityManager->getDefinitions() as $entity_type => $entity_info) {
       // Just add support for entity types which have a views integration.
-      if (isset($entity_info['base_table']) && $this->viewsData->get($entity_info['base_table']) && $this->entityManager->hasController($entity_type, 'view_builder')) {
+      if (($base_table = $entity_info->getBaseTable()) && $this->viewsData->get($base_table) && $this->entityManager->hasController($entity_type, 'view_builder')) {
         $this->derivatives[$entity_type] = array(
           'id' => 'entity:' . $entity_type,
           'provider' => 'views',
-          'title' => $entity_info['label'],
-          'help' => t('Display the @label', array('@label' => $entity_info['label'])),
-          'base' => array($entity_info['base_table']),
+          'title' => $entity_info->getLabel(),
+          'help' => t('Display the @label', array('@label' => $entity_info->getLabel())),
+          'base' => array($base_table),
           'entity_type' => $entity_type,
           'display_types' => array('normal'),
           'class' => $base_plugin_definition['class'],
diff --git a/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsLocalTask.php b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsLocalTask.php
index e106452..dd346c9 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsLocalTask.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsLocalTask.php
@@ -8,7 +8,7 @@
 namespace Drupal\views\Plugin\Derivative;
 
 use Drupal\Core\KeyValueStore\StateInterface;
-use Drupal\Core\Menu\LocalTaskDerivativeBase;
+use Drupal\Component\Plugin\Derivative\DerivativeBase;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeInterface;
 use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\views\Views;
@@ -17,7 +17,7 @@
 /**
  * Provides local task definitions for all views configured as local tasks.
  */
-class ViewsLocalTask extends LocalTaskDerivativeBase implements ContainerDerivativeInterface {
+class ViewsLocalTask extends DerivativeBase implements ContainerDerivativeInterface {
 
   /**
    * The route provider.
@@ -85,9 +85,9 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
           'title' => $menu['title'],
         ) + $base_plugin_definition;
 
-        // Default local tasks have themselves as tab root id.
+        // Default local tasks have themselves as root tab.
         if ($menu['type'] == 'default tab') {
-          $this->derivatives[$plugin_id]['tab_root_id'] = 'views_view:' . $plugin_id;
+          $this->derivatives[$plugin_id]['base_route'] = $route_name;
         }
       }
     }
@@ -95,7 +95,7 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
   }
 
   /**
-   * Alters tab_root_id and tab_parent_id into the views local tasks.
+   * Alters base_route and parent_id into the views local tasks.
    */
   public function alterLocalTasks(&$local_tasks) {
     $view_route_names = $this->state->get('views.view_route_names');
@@ -107,7 +107,7 @@ public function alterLocalTasks(&$local_tasks) {
       $executable->setDisplay($display_id);
       $menu = $executable->display_handler->getOption('menu');
 
-      // We already have set the tab_root_id for default tabs.
+      // We already have set the base_route for default tabs.
       if (in_array($menu['type'], array('tab'))) {
         $plugin_id = 'view.' . $executable->storage->id() . '.' . $display_id;
         $view_route_name = $view_route_names[$executable->storage->id() . '.' . $display_id];
@@ -128,9 +128,7 @@ public function alterLocalTasks(&$local_tasks) {
         $pattern = '/' . str_replace('%', '{}', $path);
         if ($routes = $this->routeProvider->getRoutesByPattern($pattern)) {
           foreach ($routes->all() as $name => $route) {
-            if ($parent_task = $this->getPluginIdFromRoute($name, $local_tasks)) {
-              $local_tasks['views_view:' . $plugin_id]['tab_root_id'] = $parent_task;
-            }
+            $local_tasks['views_view:' . $plugin_id]['base_route'] = $name;
             // Skip after the first found route.
             break;
           }
diff --git a/core/modules/views/lib/Drupal/views/Plugin/entity_reference/selection/ViewsSelection.php b/core/modules/views/lib/Drupal/views/Plugin/entity_reference/selection/ViewsSelection.php
index 3cc2d36..7fef5b2 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/entity_reference/selection/ViewsSelection.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/entity_reference/selection/ViewsSelection.php
@@ -66,7 +66,7 @@ public static function settingsForm(FieldDefinitionInterface $field_definition)
     $options = array();
     foreach ($displays as $data) {
       list($view, $display_id) = $data;
-      if ($view->storage->get('base_table') == $entity_info['base_table']) {
+      if ($view->storage->get('base_table') == $entity_info->getBaseTable()) {
         $name = $view->storage->get('id');
         $display = $view->storage->get('display');
         $options[$name . ':' . $display_id] = $name . ' - ' . $display[$display_id]['display_title'];
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/area/HTTPStatusCode.php b/core/modules/views/lib/Drupal/views/Plugin/views/area/HTTPStatusCode.php
index f79b2fc..d4b4d95 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/area/HTTPStatusCode.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/area/HTTPStatusCode.php
@@ -64,6 +64,7 @@ public function buildOptionsForm(&$form, &$form_state) {
   function render($empty = FALSE) {
     if (!$empty || !empty($this->options['empty'])) {
       $this->view->getResponse()->setStatusCode($this->options['status_code']);
+      $this->view->getRequest()->attributes->set('_http_statuscode', $this->options['status_code']);
     }
   }
 
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/area/View.php b/core/modules/views/lib/Drupal/views/Plugin/views/area/View.php
index 412f192..6fcf820 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/area/View.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/area/View.php
@@ -107,7 +107,7 @@ public function render($empty = FALSE) {
     if (!empty($this->options['view_to_insert'])) {
       list($view_name, $display_id) = explode(':', $this->options['view_to_insert']);
 
-      $view = $this->viewStorage->load($view_name);
+      $view = $this->viewStorage->load($view_name)->getExecutable();
 
       if (empty($view) || !$view->access($display_id)) {
         return array();
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/argument_validator/Entity.php b/core/modules/views/lib/Drupal/views/Plugin/views/argument_validator/Entity.php
index 6fdea49..1d1d958 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/argument_validator/Entity.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/argument_validator/Entity.php
@@ -8,7 +8,7 @@
 namespace Drupal\views\Plugin\views\argument_validator;
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\views\Plugin\views\argument\ArgumentPluginBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -27,7 +27,7 @@ class Entity extends ArgumentValidatorPluginBase {
   /**
    * The entity manager.
    *
-   * @var \Drupal\Core\Entity\EntityManager
+   * @var \Drupal\Core\Entity\EntityManagerInterface
    */
   protected $entityManager;
 
@@ -45,10 +45,10 @@ class Entity extends ArgumentValidatorPluginBase {
    *   The plugin_id for the plugin instance.
    * @param array $plugin_definition
    *   The plugin implementation definition.
-   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
    */
-  public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityManager $entity_manager) {
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityManagerInterface $entity_manager) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->entityManager = $entity_manager;
     $this->multipleCapable = TRUE;
@@ -90,8 +90,8 @@ public function buildOptionsForm(&$form, &$form_state) {
     // Derivative IDs are all entity:entity_type. Sanitized for js.
     // The ID is converted back on submission.
     $sanitized_id = ArgumentPluginBase::encodeValidatorId($this->definition['id']);
-    $entity_definitions = $this->entityManager->getDefinitions();
-    $bundle_type = $entity_definitions[$entity_type]['entity_keys']['bundle'];
+    $entity_info = $this->entityManager->getDefinition($entity_type);
+    $bundle_type = $entity_info->getKey('bundle');
 
     // If the entity has bundles, allow option to restrict to bundle(s).
     if ($bundle_type) {
@@ -100,8 +100,8 @@ public function buildOptionsForm(&$form, &$form_state) {
       foreach ($bundles as $bundle_id => $bundle_info) {
         $bundle_options[$bundle_id] = $bundle_info['label'];
       }
-      $bundles_title = empty($entity_definitions[$entity_type]['bundle_label']) ? t('Bundles') : $entity_definitions[$entity_type]['bundle_label'];
-      if (in_array('Drupal\Core\Entity\ContentEntityInterface', class_implements($entity_definitions[$entity_type]['class']))) {
+      $bundles_title = $entity_info->getBundleLabel() ?: $this->t('Bundles');
+      if ($entity_info->isSubclassOf('Drupal\Core\Entity\ContentEntityInterface')) {
         $fields = $this->entityManager->getFieldDefinitions($entity_type);
       }
       $bundle_name = (empty($fields) || empty($fields[$bundle_type]['label'])) ? t('bundles') : $fields[$bundle_type]['label'];
@@ -117,7 +117,7 @@ public function buildOptionsForm(&$form, &$form_state) {
     // Offer the option to filter by access to the entity in the argument.
     $form['access'] = array(
       '#type' => 'checkbox',
-      '#title' => t('Validate user has access to the %name', array('%name' => $entity_definitions[$entity_type]['label'])),
+      '#title' => t('Validate user has access to the %name', array('%name' => $entity_info->getLabel())),
       '#default_value' => $this->options['access'],
     );
     $form['operation'] = array(
@@ -138,8 +138,8 @@ public function buildOptionsForm(&$form, &$form_state) {
         '#type' => 'radios',
         '#title' => t('Multiple arguments'),
         '#options' => array(
-          0 => t('Single ID', array('%type' => $entity_definitions[$entity_type]['label'])),
-          1 => t('One or more IDs separated by , or +', array('%type' => $entity_definitions[$entity_type]['label'])),
+          0 => t('Single ID', array('%type' => $entity_info->getLabel())),
+          1 => t('One or more IDs separated by , or +', array('%type' => $entity_info->getLabel())),
         ),
         '#default_value' => (string) $this->options['multiple'],
       );
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php
index 2862d10..7c643cf 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php
@@ -1609,8 +1609,8 @@ public function buildOptionsForm(&$form, &$form_state) {
 
         $translatable_entity_tables = array();
         foreach (\Drupal::entityManager()->getDefinitions() as $entity_info) {
-          if (isset($entity_info['base_table']) && !empty($entity_info['translatable'])) {
-            $translatable_entity_tables[] = $entity_info['base_table'];
+          if ($entity_info->isTranslatable() && $base_table = $entity_info->getBaseTable()) {
+            $translatable_entity_tables[] = $base_table;
           }
         }
 
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/display/Page.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/Page.php
index 8075c27..944e0d4 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/display/Page.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/display/Page.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views\Plugin\views\display;
 
+use Drupal\Component\Utility\Xss;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
@@ -67,6 +68,22 @@ protected function defineOptions() {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  protected function getRoute($view_id, $display_id) {
+    $route = parent::getRoute($view_id, $display_id);
+
+    // Move _controller to _content for page displays, which will return a
+    // normal Drupal HTML page.
+    $defaults = $route->getDefaults();
+    $defaults['_content'] = $defaults['_controller'];
+    unset($defaults['_controller']);
+    $route->setDefaults($defaults);
+
+    return $route;
+  }
+
+  /**
    * Overrides \Drupal\views\Plugin\views\display\PathPluginBase::execute().
    */
   public function execute() {
@@ -80,12 +97,14 @@ public function execute() {
 
     // First execute the view so it's possible to get tokens for the title.
     // And the title, which is much easier.
-    $render['#title'] = filter_xss_admin($this->view->getTitle());
-
-    $response = $this->view->getResponse();
-    $response->setContent(drupal_render_page($render));
-
-    return $response;
+    // @todo Figure out how to support custom response objects. Maybe for pages
+    //   it should be dropped.
+    if (is_array($render)) {
+      $render += array(
+        '#title' => Xss::filterAdmin($this->view->getTitle()),
+      );
+    }
+    return $render;
   }
 
   /**
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/exposed_form/ExposedFormPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/exposed_form/ExposedFormPluginBase.php
index 8e4d346..f917ca5 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/exposed_form/ExposedFormPluginBase.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/exposed_form/ExposedFormPluginBase.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views\Plugin\views\exposed_form;
 
+use Drupal\views\Form\ViewsExposedForm;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Plugin\views\PluginBase;
@@ -138,7 +139,7 @@ public function renderExposedForm($block = FALSE) {
     }
 
     $form_state['exposed_form_plugin'] = $this;
-    $form = drupal_build_form('views_exposed_form', $form_state);
+    $form = \Drupal::formBuilder()->buildForm('\Drupal\views\Form\ViewsExposedForm', $form_state);
 
     if (!$this->view->display_handler->displaysExposed() || (!$block && $this->view->display_handler->getOption('exposed_block'))) {
       return array();
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/filter/Bundle.php b/core/modules/views/lib/Drupal/views/Plugin/views/filter/Bundle.php
index 402e722..bc4b201 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/filter/Bundle.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/filter/Bundle.php
@@ -29,7 +29,7 @@ class Bundle extends InOperator {
   /**
    * The entity info for the entity type.
    *
-   * @var array
+   * @var \Drupal\Core\Entity\EntityTypeInterface
    */
   protected $entityInfo;
 
@@ -40,8 +40,8 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o
     parent::init($view, $display, $options);
 
     $this->entityType = $this->getEntityType();
-    $this->entityInfo = entity_get_info($this->entityType);
-    $this->real_field = $this->entityInfo['entity_keys']['bundle'];
+    $this->entityInfo = \Drupal::entityManager()->getDefinition($this->entityType);
+    $this->real_field = $this->entityInfo->getKey('bundle');
   }
 
   /**
@@ -50,7 +50,7 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o
   public function getValueOptions() {
     if (!isset($this->value_options)) {
       $types = entity_get_bundles($this->entityType);
-      $this->value_title = t('@entity types', array('@entity' => $this->entityInfo['label']));
+      $this->value_title = t('@entity types', array('@entity' => $this->entityInfo->getLabel()));
 
       $options = array();
       foreach ($types as $type => $info) {
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php b/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php
index 265a025..424ac86 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/query/Sql.php
@@ -1257,7 +1257,7 @@ public function query($get_count = FALSE) {
 
       foreach ($entity_information as $entity_type => $info) {
         $entity_info = \Drupal::entityManager()->getDefinition($entity_type);
-        $base_field = empty($table['revision']) ? $entity_info['entity_keys']['id'] : $entity_info['entity_keys']['revision'];
+        $base_field = empty($table['revision']) ? $entity_info->getKey('id') : $entity_info->getKey('revision');
         $this->addField($info['alias'], $base_field, '', $params);
       }
     }
@@ -1494,7 +1494,7 @@ public function getEntityTableInfo() {
     // Determine which of the tables are revision tables.
     foreach ($entity_tables as $table_alias => $table) {
       $info = \Drupal::entityManager()->getDefinition($table['entity_type']);
-      if (isset($info['revision table']) && $info['revision table'] == $table['base']) {
+      if ($info->getRevisionTable() == $table['base']) {
         $entity_tables[$table_alias]['revision'] = TRUE;
       }
     }
@@ -1520,7 +1520,7 @@ function loadEntities(&$results) {
     $ids_by_type = array();
     foreach ($entity_information as $entity_type => $info) {
       $entity_info = \Drupal::entityManager()->getDefinition($entity_type);
-      $id_key = empty($table['revision']) ? $entity_info['entity_keys']['id'] : $entity_info['entity_keys']['revision'];
+      $id_key = empty($table['revision']) ? $entity_info->getKey('id') : $entity_info->getKey('revision');
       $id_alias = $this->getFieldAlias($info['alias'], $id_key);
 
       foreach ($results as $index => $result) {
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/row/EntityRow.php b/core/modules/views/lib/Drupal/views/Plugin/views/row/EntityRow.php
index 31d9e17..f7d9510 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/row/EntityRow.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/row/EntityRow.php
@@ -46,7 +46,7 @@ class EntityRow extends RowPluginBase {
   /**
    * Contains the entity info of the entity type of this row plugin instance.
    *
-   * @see entity_get_info
+   * @var \Drupal\Core\Entity\EntityTypeInterface
    */
   protected $entityInfo;
 
@@ -77,8 +77,8 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o
 
     $this->entityType = $this->definition['entity_type'];
     $this->entityInfo = $this->entityManager->getDefinition($this->entityType);
-    $this->base_table = $this->entityInfo['base_table'];
-    $this->base_field = $this->entityInfo['entity_keys']['id'];
+    $this->base_table = $this->entityInfo->getBaseTable();
+    $this->base_field = $this->entityInfo->getKey('id');
   }
 
   /**
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php
index debb036..8de866c 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php
@@ -43,9 +43,9 @@
   /**
    * Contains the information from entity_get_info of the $entity_type.
    *
-   * @var array
+   * @var \Drupal\Core\Entity\EntityTypeInterface
    */
-  protected $entity_info = array();
+  protected $entity_info;
 
   /**
    * An array of validated view objects, keyed by a hash.
@@ -118,9 +118,9 @@ public function __construct(array $configuration, $plugin_id, array $plugin_defi
 
     $this->base_table = $this->definition['base_table'];
 
-    $entities = entity_get_info();
+    $entities = \Drupal::entityManager()->getDefinitions();
     foreach ($entities as $entity_type => $entity_info) {
-      if (isset($entity_info['base_table']) && $this->base_table == $entity_info['base_table']) {
+      if ($this->base_table == $entity_info->getBaseTable()) {
         $this->entity_info = $entity_info;
         $this->entity_type = $entity_type;
       }
@@ -563,7 +563,7 @@ protected function buildFilters(&$form, &$form_state) {
 
     $bundles = entity_get_bundles($this->entity_type);
     // If the current base table support bundles and has more than one (like user).
-    if (isset($this->entity_info['bundle_keys']) && !empty($bundles)) {
+    if (!empty($bundles) && $this->entity_info && $this->entity_info->getBundleKeys()) {
       // Get all bundles and their human readable names.
       $options = array('all' => t('All'));
       foreach ($bundles as $type => $bundle) {
@@ -837,7 +837,7 @@ protected function defaultDisplayFiltersUser(array $form, array &$form_state) {
     $filters = array();
 
     if (!empty($form_state['values']['show']['type']) && $form_state['values']['show']['type'] != 'all') {
-      $bundle_key = $this->entity_info['bundle_keys']['bundle'];
+      $bundle_key = $this->entity_info->getBundleKey('bundle');
       // Figure out the table where $bundle_key lives. It may not be the same as
       // the base table for the view; the taxonomy vocabulary machine_name, for
       // example, is stored in taxonomy_vocabulary, not taxonomy_term_data.
diff --git a/core/modules/views/lib/Drupal/views/Routing/ViewPageController.php b/core/modules/views/lib/Drupal/views/Routing/ViewPageController.php
index 6fb4a7c..ba0b10b 100644
--- a/core/modules/views/lib/Drupal/views/Routing/ViewPageController.php
+++ b/core/modules/views/lib/Drupal/views/Routing/ViewPageController.php
@@ -69,6 +69,7 @@ public function handle(Request $request) {
       throw new NotFoundHttpException(String::format('Page controller for view %id requested, but view was not found.', array('%id' => $view_id)));
     }
     $view = $this->executableFactory->get($entity);
+    $view->setRequest($request);
     $view->setDisplay($display_id);
     $view->initHandlers();
 
diff --git a/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php b/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php
index d010a2b..31be4ab 100644
--- a/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/DefaultViewsTest.php
@@ -22,7 +22,7 @@ class DefaultViewsTest extends ViewTestBase {
    *
    * @var array
    */
-  public static $modules = array('views', 'node', 'search', 'comment', 'taxonomy', 'block');
+  public static $modules = array('views', 'node', 'search', 'comment', 'taxonomy', 'block', 'user');
 
   /**
    * An array of argument arrays to use for default views.
diff --git a/core/modules/views/lib/Drupal/views/Tests/Entity/FilterEntityBundleTest.php b/core/modules/views/lib/Drupal/views/Tests/Entity/FilterEntityBundleTest.php
index e41a960..db00221 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Entity/FilterEntityBundleTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Entity/FilterEntityBundleTest.php
@@ -29,13 +29,6 @@ class FilterEntityBundleTest extends ViewTestBase {
   public static $modules = array('node');
 
   /**
-   * Entity info data.
-   *
-   * @var array
-   */
-  protected $entityInfo;
-
-  /**
    * Entity bundle data.
    *
    * @var array
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/AreaEntityTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/AreaEntityTest.php
index e8f70b5..528bcda 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/AreaEntityTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/AreaEntityTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views\Tests\Handler;
 
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\views\Tests\ViewTestBase;
 use Drupal\views\Views;
 
@@ -52,8 +53,8 @@ public function testEntityAreaData() {
     $data = $this->container->get('views.views_data')->get('views');
     $entity_info = $this->container->get('entity.manager')->getDefinitions();
 
-    $expected_entities = array_filter($entity_info, function($info) {
-      return !empty($info['controllers']['view_builder']);
+    $expected_entities = array_filter($entity_info, function (EntityTypeInterface $info) {
+      return $info->hasController('view_builder');
     });
 
     // Test that all expected entity types have data.
@@ -63,8 +64,8 @@ public function testEntityAreaData() {
       $this->assertEqual($entity, $data['entity_' . $entity]['area']['entity_type'], format_string('Correct entity_type set for @entity', array('@entity' => $entity)));
     }
 
-    $expected_entities = array_filter($entity_info, function($info) {
-      return empty($info['controllers']['view_builder']);
+    $expected_entities = array_filter($entity_info, function (EntityTypeInterface $info) {
+      return !$info->hasController('view_builder');
     });
 
     // Test that no configuration entity types have data.
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/AreaViewTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/AreaViewTest.php
new file mode 100644
index 0000000..a99a316
--- /dev/null
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/AreaViewTest.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\Tests\Handler\AreaViewTest.
+ */
+
+namespace Drupal\views\Tests\Handler;
+
+use Drupal\views\Tests\ViewUnitTestBase;
+use Drupal\views\Views;
+
+/**
+ * Tests the view area handler.
+ *
+ * @see \Drupal\views\Plugin\views\area\View
+ */
+class AreaViewTest extends ViewUnitTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('user');
+
+  /**
+   * Views used by this test.
+   *
+   * @var array
+   */
+  public static $testViews = array('test_area_view', 'test_simple_argument');
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Area: View',
+      'description' => 'Tests the view area handler.',
+      'group' => 'Views Handlers',
+    );
+  }
+
+  /**
+   * Tests the view area handler.
+   */
+  public function testViewArea() {
+    $view = Views::getView('test_area_view');
+
+    $this->executeView($view);
+    $output = $view->render();
+    $output = drupal_render($output);
+    $this->assertTrue(strpos($output, 'view-test-simple-argument') !== FALSE, 'The test view is correctly embedded.');
+    $view->destroy();
+
+    $view->setArguments(array(27));
+    $this->executeView($view);
+    $output = $view->render();
+    $output = drupal_render($output);
+    $this->assertTrue(strpos($output, 'John') === FALSE, 'The test view is correctly embedded with inherited arguments.');
+    $this->assertTrue(strpos($output, 'George') !== FALSE, 'The test view is correctly embedded with inherited arguments.');
+    $view->destroy();
+  }
+
+}
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterStringTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterStringTest.php
index efb19f7..cb444c9 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterStringTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterStringTest.php
@@ -86,7 +86,7 @@ protected function getBasicPageView() {
 
     // In order to test exposed filters, we have to disable
     // the exposed forms cache.
-    drupal_static_reset('views_exposed_form_cache');
+    \Drupal::service('views.exposed_form_cache')->reset();
 
     $view->newDisplay('page', 'Page', 'page_1');
     return $view;
diff --git a/core/modules/views/lib/Drupal/views/Tests/ViewStorageTest.php b/core/modules/views/lib/Drupal/views/Tests/ViewStorageTest.php
index f9beb79..70df481 100644
--- a/core/modules/views/lib/Drupal/views/Tests/ViewStorageTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/ViewStorageTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views\Tests;
 
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\views\ViewStorageController;
 use Drupal\views\Entity\View;
 use Drupal\views\Plugin\views\display\Page;
@@ -72,11 +73,11 @@ public static function getInfo() {
    */
   function testConfigurationEntityCRUD() {
     // Get the configuration entity information and controller.
-    $this->info = entity_get_info('view');
+    $this->info = \Drupal::entityManager()->getDefinition('view');
     $this->controller = $this->container->get('entity.manager')->getStorageController('view');
 
     // Confirm that an info array has been returned.
-    $this->assertTrue(!empty($this->info) && is_array($this->info), 'The View info array is loaded.');
+    $this->assertTrue($this->info instanceof EntityTypeInterface, 'The View info array is loaded.');
 
     // Confirm we have the correct controller class.
     $this->assertTrue($this->controller instanceof ViewStorageController, 'The correct controller is loaded.');
diff --git a/core/modules/views/lib/Drupal/views/ViewExecutable.php b/core/modules/views/lib/Drupal/views/ViewExecutable.php
index 4b96efe..c9b097a 100644
--- a/core/modules/views/lib/Drupal/views/ViewExecutable.php
+++ b/core/modules/views/lib/Drupal/views/ViewExecutable.php
@@ -11,6 +11,7 @@
 use Drupal\views\Plugin\views\query\QueryPluginBase;
 use Drupal\views\ViewStorageInterface;
 use Drupal\Component\Utility\Tags;
+use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 
 /**
@@ -335,6 +336,13 @@ class ViewExecutable {
   protected $response = NULL;
 
   /**
+   * Stores the current request object.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
    * Does this view already have loaded it's handlers.
    *
    * @todo Group with other static properties.
@@ -1569,7 +1577,7 @@ public function setResponse(Response $response) {
   /**
    * Gets the response object used by the view.
    *
-   * @return Symfony\Component\HttpFoundation\Response
+   * @return \Symfony\Component\HttpFoundation\Response
    *   The response object of the view.
    */
   public function getResponse() {
@@ -1580,6 +1588,26 @@ public function getResponse() {
   }
 
   /**
+   * Sets the request object.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   */
+  public function setRequest(Request $request) {
+    $this->request = $request;
+  }
+
+  /**
+   * Gets the request object.
+   *
+   * @return \Symfony\Component\HttpFoundation\Request $request
+   *   Returns the request object.
+   */
+  public function getRequest() {
+    return $this->request;
+  }
+
+  /**
    * Get the view's current title. This can change depending upon how it
    * was built.
    */
diff --git a/core/modules/views/tests/Drupal/views/Tests/EventSubscriber/RouteSubscriberTest.php b/core/modules/views/tests/Drupal/views/Tests/EventSubscriber/RouteSubscriberTest.php
index a7759a7..e5d6c69 100644
--- a/core/modules/views/tests/Drupal/views/Tests/EventSubscriber/RouteSubscriberTest.php
+++ b/core/modules/views/tests/Drupal/views/Tests/EventSubscriber/RouteSubscriberTest.php
@@ -92,7 +92,7 @@ public function testDynamicRoutes() {
     $this->state->expects($this->once())
       ->method('set')
       ->with('views.view_route_names', array('test_id.page_1' => 'views.test_id.page_1', 'test_id.page_2' => 'views.test_id.page_2'));
-    $this->routeSubscriber->destruct();
+    $this->routeSubscriber->routeRebuildFinished();
   }
 
   /**
@@ -135,7 +135,7 @@ public function testOnAlterRoutes() {
     $this->state->expects($this->once())
       ->method('set')
       ->with('views.view_route_names', array('test_id.page_1' => 'test_route', 'test_id.page_2' => 'views.test_id.page_2'));
-    $this->routeSubscriber->destruct();
+    $this->routeSubscriber->routeRebuildFinished();
   }
 
   /**
diff --git a/core/modules/views/tests/Drupal/views/Tests/Plugin/Derivative/ViewsLocalTaskTest.php b/core/modules/views/tests/Drupal/views/Tests/Plugin/Derivative/ViewsLocalTaskTest.php
index 822f609..2850b92 100644
--- a/core/modules/views/tests/Drupal/views/Tests/Plugin/Derivative/ViewsLocalTaskTest.php
+++ b/core/modules/views/tests/Drupal/views/Tests/Plugin/Derivative/ViewsLocalTaskTest.php
@@ -139,7 +139,7 @@ public function testGetDerivativeDefinitionsWithLocalTask() {
     $this->assertEquals(12, $definitions['view.example_view.page_1']['weight']);
     $this->assertEquals('Example title', $definitions['view.example_view.page_1']['title']);
     $this->assertEquals($this->baseDefinition['class'], $definitions['view.example_view.page_1']['class']);
-    $this->assertTrue(empty($definitions['view.example_view.page_1']['tab_root_id']));
+    $this->assertTrue(empty($definitions['view.example_view.page_1']['base_route']));
   }
 
   /**
@@ -226,7 +226,7 @@ public function testGetDerivativeDefinitionsWithDefaultLocalTask() {
     $this->assertEquals(12, $plugin['weight']);
     $this->assertEquals('Example title', $plugin['title']);
     $this->assertEquals($this->baseDefinition['class'], $plugin['class']);
-    $this->assertEquals('views_view:view.example_view.page_1', $plugin['tab_root_id']);
+    $this->assertEquals('view.example_view.page_1', $plugin['base_route']);
 
     // Setup the prefix of the derivative.
     $definitions['views_view:view.example_view.page_1'] = $definitions['view.example_view.page_1'];
@@ -239,7 +239,7 @@ public function testGetDerivativeDefinitionsWithDefaultLocalTask() {
     $this->assertEquals(12, $plugin['weight']);
     $this->assertEquals('Example title', $plugin['title']);
     $this->assertEquals($this->baseDefinition['class'], $plugin['class']);
-    $this->assertEquals('views_view:view.example_view.page_1', $plugin['tab_root_id']);
+    $this->assertEquals('view.example_view.page_1', $plugin['base_route']);
   }
 
   /**
@@ -295,7 +295,7 @@ public function testGetDerivativeDefinitionsWithExistingLocalTask() {
     $definitions['test_route_tab'] = $other_tab = array(
       'route_name' => 'test_route',
       'title' => 'Test route',
-      'tab_root_id' => 'test_route_tab',
+      'base_route' => 'test_route',
     );
 
     $definitions += $this->localTaskDerivative->getDerivativeDefinitions($this->baseDefinition);
@@ -315,7 +315,7 @@ public function testGetDerivativeDefinitionsWithExistingLocalTask() {
     $this->assertEquals(12, $plugin['weight']);
     $this->assertEquals('Example title', $plugin['title']);
     $this->assertEquals($this->baseDefinition['class'], $plugin['class']);
-    $this->assertEquals('test_route_tab', $plugin['tab_root_id']);
+    $this->assertEquals('test_route', $plugin['base_route']);
   }
 
 }
diff --git a/core/modules/views/tests/Drupal/views/Tests/Plugin/argument_validator/EntityTest.php b/core/modules/views/tests/Drupal/views/Tests/Plugin/argument_validator/EntityTest.php
index 15d44f6..c6a9104 100644
--- a/core/modules/views/tests/Drupal/views/Tests/Plugin/argument_validator/EntityTest.php
+++ b/core/modules/views/tests/Drupal/views/Tests/Plugin/argument_validator/EntityTest.php
@@ -37,7 +37,7 @@ class EntityTest extends UnitTestCase {
   /**
    * The entity manager.
    *
-   * @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Entity\EntityManager
+   * @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Entity\EntityManagerInterface
    */
   protected $entityManager;
 
@@ -62,9 +62,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->entityManager = $this->getMockBuilder('Drupal\Core\Entity\EntityManager')
-      ->disableOriginalConstructor()
-      ->getMock();
+    $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
 
     $mock_entity = $this->getMockBuilder('Drupal\Core\Entity\Entity')
       ->disableOriginalConstructor()
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_view.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_view.yml
new file mode 100644
index 0000000..ffd5173
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_view.yml
@@ -0,0 +1,50 @@
+base_table: views_test_data
+core: '8'
+description: ''
+status: '1'
+display:
+  default:
+    display_options:
+      defaults:
+        fields: '0'
+        pager: '0'
+        pager_options: '0'
+        sorts: '0'
+      fields:
+        id:
+          field: id
+          id: id
+          relationship: none
+          table: views_test_data
+          plugin_id: numeric
+          provider: views_test_data
+      pager:
+        options:
+          offset: '0'
+        type: none
+      pager_options: {  }
+      sorts:
+        id:
+          field: id
+          id: id
+          order: ASC
+          relationship: none
+          table: views_test_data
+          plugin_id: numeric
+          provider: views_test_data
+      header:
+        view:
+          field: view
+          id: view
+          table: views
+          view_to_insert: test_simple_argument:default
+          inherit_arguments: 1
+          plugin_id: view
+          provider: views
+    display_plugin: default
+    display_title: Master
+    id: default
+    position: '0'
+label: ''
+id: test_area_view
+tag: ''
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index 4ac7613..daf2406 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -13,6 +13,7 @@
 use Drupal\Core\Database\Query\AlterableInterface;
 use Drupal\Core\Language\Language;
 use Drupal\views\Plugin\Derivative\ViewsLocalTask;
+use Drupal\Core\Template\AttributeArray;
 use Drupal\views\ViewExecutable;
 use Drupal\Component\Plugin\Exception\PluginException;
 use Drupal\views\Entity\View;
@@ -414,7 +415,7 @@ function views_page_alter(&$page) {
 /**
  * Implements MODULE_preprocess_HOOK().
  */
-function views_preprocess_html(&$variables) {
+function views_preprocess_page(&$variables) {
   // Early-return to prevent adding unnecessary JavaScript.
   if (!user_access('access contextual links')) {
     return;
@@ -433,10 +434,17 @@ function views_preprocess_html(&$variables) {
   // remove the "contextual-region" class from the <body> tag here and add
   // JavaScript that will insert it back in the correct place.
   if (!empty($variables['page']['#views_contextual_links'])) {
-    $key = array_search('contextual-region', $variables['attributes']['class']);
+    /** @var \Drupal\Core\Page\HtmlPage $page_object */
+    $page_object = $variables['page']['#page'];
+    $attributes = $page_object->getBodyAttributes();
+    $class = $attributes['class'] ?: array();
+
+    $key = array_search('contextual-region', $variables['attributes']['class'] instanceof AttributeArray ? $variables['attributes']['class']->value() : $variables['attributes']['class']);
     if ($key !== FALSE) {
-      unset($variables['attributes']['class'][$key]);
-      $variables['attributes']['data-views-page-contextual-id'] = $variables['title_suffix']['contextual_links']['#id'];
+      /** @var \Drupal\Core\Page\HtmlPage $page_object */
+      unset($class[$key]);
+      $attributes['class'] = $class;
+      $attributes['data-views-page-contextual-id'] = $variables['title_suffix']['contextual_links']['#id'];
       drupal_add_library('views', 'views.contextual-links');
     }
   }
@@ -1053,83 +1061,6 @@ function views_pre_render_views_form_views_form($element) {
 }
 
 /**
- * Form builder for the exposed widgets form.
- *
- * Be sure that $display is a reference.
- */
-function views_exposed_form($form, &$form_state) {
-  // Don't show the form when batch operations are in progress.
-  if ($batch = batch_get() && isset($batch['current_set'])) {
-    return array(
-      // Set the theme callback to be nothing to avoid errors in template_preprocess_views_exposed_form().
-      '#theme' => '',
-    );
-  }
-
-  // Make sure that we validate because this form might be submitted
-  // multiple times per page.
-  $form_state['must_validate'] = TRUE;
-  $view = $form_state['view'];
-  $display = &$form_state['display'];
-
-  $form_state['input'] = $view->getExposedInput();
-
-  // Let form plugins know this is for exposed widgets.
-  $form_state['exposed'] = TRUE;
-  // Check if the form was already created
-  if ($cache = views_exposed_form_cache($view->storage->id(), $view->current_display)) {
-    return $cache;
-  }
-
-  $form['#info'] = array();
-
-  // Go through each handler and let it generate its exposed widget.
-  foreach ($view->display_handler->handlers as $type => $value) {
-    foreach ($view->$type as $id => $handler) {
-      if ($handler->canExpose() && $handler->isExposed()) {
-        // Grouped exposed filters have their own forms.
-        // Instead of render the standard exposed form, a new Select or
-        // Radio form field is rendered with the available groups.
-        // When an user choose an option the selected value is split
-        // into the operator and value that the item represents.
-        if ($handler->isAGroup()) {
-          $handler->groupForm($form, $form_state);
-          $id = $handler->options['group_info']['identifier'];
-        }
-        else {
-          $handler->buildExposedForm($form, $form_state);
-        }
-        if ($info = $handler->exposedInfo()) {
-          $form['#info']["$type-$id"] = $info;
-        }
-      }
-    }
-  }
-
-  $form['actions'] = array('#type' => 'actions');
-  $form['actions']['submit'] = array(
-    // Prevent from showing up in \Drupal::request()->query.
-    '#name' => '',
-    '#type' => 'submit',
-    '#value' => t('Apply'),
-    '#id' => drupal_html_id('edit-submit-' . $view->storage->id()),
-  );
-
-  $form['#action'] = url($view->display_handler->getUrl());
-  $form['#theme'] = $view->buildThemeFunctions('views_exposed_form');
-  $form['#id'] = drupal_clean_css_identifier('views_exposed_form-' . check_plain($view->storage->id()) . '-' . check_plain($display['id']));
-//  $form['#attributes']['class'] = array('views-exposed-form');
-
-  $exposed_form_plugin = $form_state['exposed_form_plugin'];
-  $exposed_form_plugin->exposedFormAlter($form, $form_state);
-
-  // Save the form
-  views_exposed_form_cache($view->storage->id(), $view->current_display, $form);
-
-  return $form;
-}
-
-/**
  * Implement hook_form_alter for the exposed form.
  *
  * Since the exposed form is a GET form, we don't want it to send a wide
@@ -1142,71 +1073,6 @@ function views_form_views_exposed_form_alter(&$form, &$form_state) {
 }
 
 /**
- * Validate handler for exposed filters
- */
-function views_exposed_form_validate(&$form, &$form_state) {
-  foreach (array('field', 'filter') as $type) {
-    $handlers = &$form_state['view']->$type;
-    foreach ($handlers as $key => $handler) {
-      $handlers[$key]->validateExposed($form, $form_state);
-    }
-  }
-  $exposed_form_plugin = $form_state['exposed_form_plugin'];
-  $exposed_form_plugin->exposedFormValidate($form, $form_state);
-}
-
-/**
- * Submit handler for exposed filters
- */
-function views_exposed_form_submit(&$form, &$form_state) {
-  foreach (array('field', 'filter') as $type) {
-    $handlers = &$form_state['view']->$type;
-    foreach ($handlers as $key => $info) {
-      $handlers[$key]->submitExposed($form, $form_state);
-    }
-  }
-  $form_state['view']->exposed_data = $form_state['values'];
-  $form_state['view']->exposed_raw_input = array();
-
-  $exclude = array('submit', 'form_build_id', 'form_id', 'form_token', 'exposed_form_plugin', '', 'reset');
-  $exposed_form_plugin = $form_state['exposed_form_plugin'];
-  $exposed_form_plugin->exposedFormSubmit($form, $form_state, $exclude);
-
-  foreach ($form_state['values'] as $key => $value) {
-    if (!in_array($key, $exclude)) {
-      $form_state['view']->exposed_raw_input[$key] = $value;
-    }
-  }
-}
-
-/**
- * Save the Views exposed form for later use.
- *
- * @param $views_name
- *   String. The views name.
- * @param $display_name
- *   String. The current view display name.
- * @param $form_output
- *   Array (optional). The form structure. Only needed when inserting the value.
- * @return
- *   Array. The form structure, if any. Otherwise, return FALSE.
- */
-function views_exposed_form_cache($views_name, $display_name, $form_output = NULL) {
-  // When running tests for exposed filters, this cache should
-  // be cleared between each test.
-  $views_exposed = &drupal_static(__FUNCTION__);
-
-  // Save the form output
-  if (!empty($form_output)) {
-    $views_exposed[$views_name][$display_name] = $form_output;
-    return;
-  }
-
-  // Return the form output, if any
-  return empty($views_exposed[$views_name][$display_name]) ? FALSE : $views_exposed[$views_name][$display_name];
-}
-
-/**
  * Implements hook_query_TAG_alter().
  *
  * This is the hook_query_alter() for queries tagged by Views and is used to
diff --git a/core/modules/views/views.services.yml b/core/modules/views/views.services.yml
index c8e7482..30aa96a 100644
--- a/core/modules/views/views.services.yml
+++ b/core/modules/views/views.services.yml
@@ -87,8 +87,9 @@ services:
     arguments: ['@entity.manager', '@state']
     tags:
       - { name: 'event_subscriber' }
-      - { name: 'needs_destruction' }
   views.route_access_check:
     class: Drupal\views\ViewsAccessCheck
     tags:
       - { name: 'access_check' }
+  views.exposed_form_cache:
+    class: Drupal\views\ExposedFormCache
diff --git a/core/modules/views/views.views.inc b/core/modules/views/views.views.inc
index edf844a..02e41cb 100644
--- a/core/modules/views/views.views.inc
+++ b/core/modules/views/views.views.inc
@@ -113,10 +113,10 @@ function views_views_data() {
   );
 
   // Registers an entity area handler per entity type.
-  foreach (entity_get_info() as $entity_type => $entity_info) {
+  foreach (\Drupal::entityManager()->getDefinitions() as $entity_type => $entity_info) {
     // Exclude entity types, which cannot be rendered.
-    if (!empty($entity_info['controllers']['view_builder'])) {
-      $label = $entity_info['label'];
+    if ($entity_info->hasController('view_builder')) {
+      $label = $entity_info->getLabel();
       $data['views']['entity_' . $entity_type] = array(
         'title' => t('Rendered entity - @label', array('@label' => $label)),
         'help' => t('Displays a rendered @label entity in an area.', array('@label' => $label)),
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php
index ed9d983..b45efe7 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Config\Entity\ConfigEntityListController;
 use Drupal\Core\Entity\EntityControllerInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -31,10 +32,9 @@ class ViewListController extends ConfigEntityListController implements EntityCon
   /**
    * {@inheritdoc}
    */
-  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_info) {
     return new static(
-      $entity_type,
-      $container->get('entity.manager')->getStorageController($entity_type),
+      $container->get('entity.manager')->getStorageController($entity_info->id()),
       $entity_info,
       $container->get('plugin.manager.views.display'),
       $container->get('module_handler')
@@ -44,19 +44,17 @@ public static function createInstance(ContainerInterface $container, $entity_typ
   /**
    * Constructs a new EntityListController object.
    *
-   * @param string $entity_type.
-   *   The type of entity to be listed.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage.
    *   The entity storage controller class.
-   * @param array $entity_info
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
    *   An array of entity info for this entity type.
    * @param \Drupal\Component\Plugin\PluginManagerInterface $display_manager
    *   The views display plugin manager to use.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler.
    */
-  public function __construct($entity_type, EntityStorageControllerInterface $storage, $entity_info, PluginManagerInterface $display_manager, ModuleHandlerInterface $module_handler) {
-    parent::__construct($entity_type, $entity_info, $storage, $module_handler);
+  public function __construct(EntityStorageControllerInterface $storage, EntityTypeInterface $entity_info, PluginManagerInterface $display_manager, ModuleHandlerInterface $module_handler) {
+    parent::__construct($entity_info, $storage, $module_handler);
 
     $this->displayManager = $display_manager;
   }
diff --git a/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php b/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php
index e170fc8..d058288 100644
--- a/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php
+++ b/core/modules/views_ui/tests/Drupal/views_ui/Tests/ViewListControllerTest.php
@@ -31,7 +31,6 @@ public function testBuildRowEntityList() {
     $storage_controller = $this->getMockBuilder('Drupal\views\ViewStorageController')
       ->disableOriginalConstructor()
       ->getMock();
-    $entity_info = array();
     $display_manager = $this->getMockBuilder('\Drupal\views\Plugin\ViewsPluginManager')
       ->disableOriginalConstructor()
       ->getMock();
@@ -121,13 +120,12 @@ public function testBuildRowEntityList() {
     $container->set('string_translation', $this->getStringTranslationStub());
     \Drupal::setContainer($container);
 
-    $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
-      ->disableOriginalConstructor()
-      ->getMock();
+    $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
 
     // Setup a view list controller with a mocked buildOperations method,
     // because t() is called on there.
-    $view_list_controller = $this->getMock('Drupal\views_ui\ViewListController', array('buildOperations'), array('view', $storage_controller, $entity_info, $display_manager, $module_handler));
+    $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+    $view_list_controller = $this->getMock('Drupal\views_ui\ViewListController', array('buildOperations'), array($storage_controller, $entity_type, $display_manager, $module_handler));
     $view_list_controller->expects($this->any())
       ->method('buildOperations')
       ->will($this->returnValue(array()));
diff --git a/core/modules/views_ui/views_ui.local_tasks.yml b/core/modules/views_ui/views_ui.local_tasks.yml
index 028ebc2..9ef586e 100644
--- a/core/modules/views_ui/views_ui.local_tasks.yml
+++ b/core/modules/views_ui/views_ui.local_tasks.yml
@@ -1,32 +1,30 @@
 views_ui.settings_tab:
   route_name: views_ui.settings_basic
   title: Settings
-  tab_root_id: views_ui.list_tab
+  base_route: views_ui.list
 
 views_ui.settings_basic_tab:
   route_name: views_ui.settings_basic
   title: Basic
-  tab_root_id: views_ui.list_tab
-  tab_parent_id: views_ui.settings_tab
+  parent_id: views_ui.settings_tab
 
 views_ui.settings_advanced_tab:
   route_name: views_ui.settings_advanced
   title: Advanced
-  tab_root_id: views_ui.list_tab
-  tab_parent_id: views_ui.settings_tab
+  parent_id: views_ui.settings_tab
   weight: 10
 
 views_ui.list_tab:
   route_name: views_ui.list
   title: List
-  tab_root_id: views_ui.list_tab
+  base_route: views_ui.list
 
 views_ui.reports_fields:
   route_name: views_ui.reports_fields
   title: 'Used in views'
-  tab_root_id: field_ui.list
+  base_route: field_ui.list
 
 views_ui.edit:
   title: 'Edit'
   route_name: views_ui.edit
-  tab_root_id: views_ui.edit
+  base_route: views_ui.edit
diff --git a/core/modules/views_ui/views_ui.module b/core/modules/views_ui/views_ui.module
index c175caa..5afbfc5 100644
--- a/core/modules/views_ui/views_ui.module
+++ b/core/modules/views_ui/views_ui.module
@@ -13,6 +13,31 @@
 use Drupal\Core\Ajax\ReplaceCommand;
 
 /**
+ * Implements hook_help().
+ */
+function views_ui_help($path, $arg) {
+
+  switch ($path) {
+    case 'admin/help#views_ui':
+      $output = '';
+      $output .= '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('The Views UI module provides an interface for managing views for the <a href="@views">Views module</a>. For more information, see the <a href="@handbook">online documentation for the Views UI module</a>.', array('@views' => \Drupal::url('help.page', array('name' => 'views')), '@handbook' => 'https://drupal.org/documentation/modules/views_ui')) . '</p>';
+      $output .= '<h3>' . t('Uses') . '</h3>';
+      $output .= '<dl>';
+      $output .= '<dt>' . t('Creating and managing views.') . '</dt>';
+      $output .= '<dd>' . t('Views can be created from the <a href="@list">Views list page</a> by using the "Add new view" action. Existing views can be managed from the <a href="@list">Views list page</a> by locating the view in the "Enabled" or "Disabled" list and selecting the desired operation action, for example "Edit".', array('@list' => \Drupal::url('views_ui.list', array('name' => 'views_ui')))) . '</dd>';
+      $output .= '<dt>' . t('Enabling and disabling views.') . '<dt>';
+      $output .= '<dd>' . t('Views can be enabled or disabled from the <a href="@list">Views list page</a>. To enable a view, find the view within the "Disabled" list and select the "Enable" operation. To disable a view find the view within the "Enabled" list and select the "Disable" operation.', array('@list' => \Drupal::url('views_ui.list', array('name' => 'views_ui')))) . '</dd>';
+      $output .= '<dt>' . t('Exporting and importing views.') . '</dt>';
+      $output .= '<dd>' . t('Views can be exported and imported as configuration files by using the <a href="@config">Configuration Manager module</a>.', array('@config' => \Drupal::url('help.page', array('name' => 'config')))) . '</dd>';
+      $output .= '<dt>' . t('Theming views.') . '</dt>';
+      $output .= '<dd>' . t('The template files used by views can be overridden from a custom theme. When editing a view, you can see the templates that are used and alternatives for overriding them by clicking on the "Templates" link, found in the Advanced &gt; Other section under "Output".') . '</dd>';
+      $output .= '</dl>';
+      return $output;
+  }
+}
+
+/**
  * Implements hook_menu().
  */
 function views_ui_menu() {
@@ -39,18 +64,16 @@ function views_ui_menu() {
  * Implements hook_entity_info().
  */
 function views_ui_entity_info(&$entity_info) {
-  $entity_info['view']['controllers'] += array(
-    'list' => 'Drupal\views_ui\ViewListController',
-    'form' => array(
-      'edit' => 'Drupal\views_ui\ViewEditFormController',
-      'add' => 'Drupal\views_ui\ViewAddFormController',
-      'preview' => 'Drupal\views_ui\ViewPreviewFormController',
-      'clone' => 'Drupal\views_ui\ViewCloneFormController',
-      'delete' => 'Drupal\views_ui\ViewDeleteFormController',
-      'break_lock' => 'Drupal\views_ui\Form\BreakLockForm',
-    ),
-  );
-  $entity_info['view']['links']['edit-form'] = 'views_ui.edit';
+  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
+  $entity_info['view']
+    ->setForm('edit', 'Drupal\views_ui\ViewEditFormController')
+    ->setForm('add', 'Drupal\views_ui\ViewAddFormController')
+    ->setForm('preview', 'Drupal\views_ui\ViewPreviewFormController')
+    ->setForm('clone', 'Drupal\views_ui\ViewCloneFormController')
+    ->setForm('delete', 'Drupal\views_ui\ViewDeleteFormController')
+    ->setForm('break_lock', 'Drupal\views_ui\Form\BreakLockForm')
+    ->setList('Drupal\views_ui\ViewListController')
+    ->setLinkTemplate('edit-form', 'views_ui.edit');
 }
 
 /**
diff --git a/core/modules/views_ui/views_ui.routing.yml b/core/modules/views_ui/views_ui.routing.yml
index 3d3be39..1d8128a 100644
--- a/core/modules/views_ui/views_ui.routing.yml
+++ b/core/modules/views_ui/views_ui.routing.yml
@@ -127,7 +127,7 @@ views_ui.form_add_item:
       view:
         tempstore: TRUE
   defaults:
-    _controller: '\Drupal\views_ui\Form\Ajax\AddItem::getForm'
+    _content: '\Drupal\views_ui\Form\Ajax\AddItem::getForm'
   requirements:
     _permission: 'administer views'
     js: 'nojs|ajax'
@@ -139,7 +139,7 @@ views_ui.form_edit_details:
       view:
         tempstore: TRUE
   defaults:
-    _controller: '\Drupal\views_ui\Form\Ajax\EditDetails::getForm'
+    _content: '\Drupal\views_ui\Form\Ajax\EditDetails::getForm'
   requirements:
     _permission: 'administer views'
     js: 'nojs|ajax'
@@ -151,7 +151,7 @@ views_ui.form_reorder_displays:
       view:
         tempstore: TRUE
   defaults:
-    _controller: '\Drupal\views_ui\Form\Ajax\ReorderDisplays::getForm'
+    _content: '\Drupal\views_ui\Form\Ajax\ReorderDisplays::getForm'
   requirements:
     _permission: 'administer views'
     js: 'nojs|ajax'
@@ -163,7 +163,7 @@ views_ui.form_analyze:
       view:
         tempstore: TRUE
   defaults:
-    _controller: '\Drupal\views_ui\Form\Ajax\Analyze::getForm'
+    _content: '\Drupal\views_ui\Form\Ajax\Analyze::getForm'
   requirements:
     _permission: 'administer views'
     js: 'nojs|ajax'
@@ -175,7 +175,7 @@ views_ui.form_rearrange:
       view:
         tempstore: TRUE
   defaults:
-    _controller: '\Drupal\views_ui\Form\Ajax\Rearrange::getForm'
+    _content: '\Drupal\views_ui\Form\Ajax\Rearrange::getForm'
   requirements:
     _permission: 'administer views'
     js: 'nojs|ajax'
@@ -187,7 +187,7 @@ views_ui.form_rearrange_filter:
       view:
         tempstore: TRUE
   defaults:
-    _controller: '\Drupal\views_ui\Form\Ajax\RearrangeFilter::getForm'
+    _content: '\Drupal\views_ui\Form\Ajax\RearrangeFilter::getForm'
   requirements:
     _permission: 'administer views'
     js: 'nojs|ajax'
@@ -199,7 +199,7 @@ views_ui.form_display:
       view:
         tempstore: TRUE
   defaults:
-    _controller: '\Drupal\views_ui\Form\Ajax\Display::getForm'
+    _content: '\Drupal\views_ui\Form\Ajax\Display::getForm'
   requirements:
     _permission: 'administer views'
     js: 'nojs|ajax'
@@ -211,7 +211,7 @@ views_ui.form_config_item:
       view:
         tempstore: TRUE
   defaults:
-    _controller: '\Drupal\views_ui\Form\Ajax\ConfigItem::getForm'
+    _content: '\Drupal\views_ui\Form\Ajax\ConfigItem::getForm'
   requirements:
     _permission: 'administer views'
     js: 'nojs|ajax'
@@ -223,7 +223,7 @@ views_ui.form_config_item_extra:
       view:
         tempstore: TRUE
   defaults:
-    _controller: '\Drupal\views_ui\Form\Ajax\ConfigItemExtra::getForm'
+    _content: '\Drupal\views_ui\Form\Ajax\ConfigItemExtra::getForm'
   requirements:
     _permission: 'administer views'
     js: 'nojs|ajax'
@@ -235,7 +235,7 @@ views_ui.form_config_item_group:
       view:
         tempstore: TRUE
   defaults:
-    _controller: '\Drupal\views_ui\Form\Ajax\ConfigItemGroup::getForm'
+    _content: '\Drupal\views_ui\Form\Ajax\ConfigItemGroup::getForm'
     form_state: NULL
   requirements:
     _permission: 'administer views'
diff --git a/core/profiles/standard/config/filter.format.basic_html.yml b/core/profiles/standard/config/filter.format.basic_html.yml
index 276ac0c..f5acb95 100644
--- a/core/profiles/standard/config/filter.format.basic_html.yml
+++ b/core/profiles/standard/config/filter.format.basic_html.yml
@@ -9,7 +9,7 @@ cache: true
 filters:
   filter_html:
     id: filter_html
-    module: filter
+    provider: filter
     status: true
     weight: -10
     settings:
@@ -18,19 +18,19 @@ filters:
       filter_html_nofollow: false
   filter_caption:
     id: filter_caption
-    module: filter
+    provider: filter
     status: 1
     weight: 8
     settings: {  }
   filter_html_image_secure:
     id: filter_html_image_secure
-    module: filter
+    provider: filter
     status: true
     weight: 9
     settings: {  }
   filter_htmlcorrector:
     id: filter_htmlcorrector
-    module: filter
+    provider: filter
     status: true
     weight: 10
     settings: {  }
diff --git a/core/profiles/standard/config/filter.format.full_html.yml b/core/profiles/standard/config/filter.format.full_html.yml
index 86bf71e..37daecc 100644
--- a/core/profiles/standard/config/filter.format.full_html.yml
+++ b/core/profiles/standard/config/filter.format.full_html.yml
@@ -9,13 +9,13 @@ cache: true
 filters:
   filter_caption:
     id: filter_caption
-    module: filter
+    provider: filter
     status: true
     weight: 9
     settings: {  }
   filter_htmlcorrector:
     id: filter_htmlcorrector
-    module: filter
+    provider: filter
     status: true
     weight: 10
     settings: {  }
diff --git a/core/profiles/standard/config/filter.format.restricted_html.yml b/core/profiles/standard/config/filter.format.restricted_html.yml
index 4284574..36ca00e 100644
--- a/core/profiles/standard/config/filter.format.restricted_html.yml
+++ b/core/profiles/standard/config/filter.format.restricted_html.yml
@@ -9,7 +9,7 @@ cache: true
 filters:
   filter_html:
     id: filter_html
-    module: filter
+    provider: filter
     status: true
     weight: -10
     settings:
@@ -18,20 +18,20 @@ filters:
       filter_html_nofollow: false
   filter_autop:
     id: filter_autop
-    module: filter
+    provider: filter
     status: true
     weight: 0
     settings: {  }
   filter_url:
     id: filter_url
-    module: filter
+    provider: filter
     status: true
     weight: 0
     settings:
       filter_url_length: 72
   filter_htmlcorrector:
     id: filter_htmlcorrector
-    module: filter
+    provider: filter
     status: true
     weight: 10
     settings: {  }
diff --git a/core/profiles/standard/config/node.type.article.yml b/core/profiles/standard/config/node.type.article.yml
index dd2394f..a3e10b2 100644
--- a/core/profiles/standard/config/node.type.article.yml
+++ b/core/profiles/standard/config/node.type.article.yml
@@ -9,10 +9,10 @@ settings:
   node:
     preview: '1'
     options:
-      status: status
-      promote: promote
-      sticky: '0'
-      revision: '0'
-    submitted: '1'
+      status: true
+      promote: true
+      sticky: false
+      revision: false
+    submitted: true
 status: '1'
 langcode: en
diff --git a/core/profiles/standard/config/node.type.page.yml b/core/profiles/standard/config/node.type.page.yml
index 48a919a..d41ae94 100644
--- a/core/profiles/standard/config/node.type.page.yml
+++ b/core/profiles/standard/config/node.type.page.yml
@@ -9,10 +9,10 @@ settings:
   node:
     preview: '1'
     options:
-      status: status
-      promote: '0'
-      sticky: '0'
-      revision: '0'
-    submitted: '0'
+      status: true
+      promote: false
+      sticky: false
+      revision: false
+    submitted: false
 status: '1'
 langcode: en
diff --git a/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
index 689cdf9..4c2e0fe 100644
--- a/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
@@ -352,7 +352,7 @@ public function testCheckConjunctions($conjunction, $name, $condition_one, $cond
     $this->setupAccessChecker();
     $access_check = new DefinedTestAccessCheck();
     $this->container->register('test_access_defined', $access_check);
-    $this->accessManager->addCheckService('test_access_defined');
+    $this->accessManager->addCheckService('test_access_defined', array('_test_access'));
 
     $request = new Request();
 
@@ -371,21 +371,6 @@ public function testCheckConjunctions($conjunction, $name, $condition_one, $cond
   }
 
   /**
-   * Tests the static access checker interface.
-   */
-  public function testStaticAccessCheckInterface() {
-    $mock_static = $this->getMock('Drupal\Core\Access\StaticAccessCheckInterface');
-    $mock_static->expects($this->once())
-      ->method('appliesTo')
-      ->will($this->returnValue(array('_access')));
-
-    $this->container->set('test_static_access', $mock_static);
-    $this->accessManager->addCheckService('test_static_access');
-
-    $this->accessManager->setChecks($this->routeCollection);
-  }
-
-  /**
    * Tests the checkNamedRoute method.
    *
    * @see \Drupal\Core\Access\AccessManager::checkNamedRoute()
@@ -593,7 +578,7 @@ protected function setupAccessChecker() {
     $this->accessManager->setContainer($this->container);
     $access_check = new DefaultAccessCheck();
     $this->container->register('test_access_default', $access_check);
-    $this->accessManager->addCheckService('test_access_default');
+    $this->accessManager->addCheckService('test_access_default', array('_access'));
   }
 
 }
diff --git a/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php
index 0d7a406..de621887 100644
--- a/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php
@@ -63,13 +63,6 @@ public function setUp() {
   }
 
   /**
-   * Tests CsrfAccessCheck::appliesTo().
-   */
-  public function testAppliesTo() {
-    $this->assertEquals($this->accessCheck->appliesTo(), array('_csrf_token'), 'Access checker returned the expected appliesTo() array.');
-  }
-
-  /**
    * Tests the access() method with a valid token.
    */
   public function testAccessTokenPass() {
diff --git a/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php
index 8144c23..19a8bff 100644
--- a/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php
@@ -52,14 +52,6 @@ protected function setUp() {
     $this->accessChecker = new CustomAccessCheck($this->controllerResolver);
   }
 
-
-  /**
-   * Tests the appliesTo method.
-   */
-  public function testAppliesTo() {
-    $this->assertEquals($this->accessChecker->appliesTo(), array('_custom_access'));
-  }
-
   /**
    * Test the access method.
    */
diff --git a/core/tests/Drupal/Tests/Core/Access/DefaultAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Access/DefaultAccessCheckTest.php
index a2fce18..d452aa1 100644
--- a/core/tests/Drupal/Tests/Core/Access/DefaultAccessCheckTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/DefaultAccessCheckTest.php
@@ -52,14 +52,6 @@ protected function setUp() {
     $this->accessChecker = new DefaultAccessCheck();
   }
 
-
-  /**
-   * Tests the appliesTo method.
-   */
-  public function testAppliesTo() {
-    $this->assertEquals($this->accessChecker->appliesTo(), array('_access'), 'Access checker returned the expected appliesTo() array.');
-  }
-
   /**
    * Test the access method.
    */
diff --git a/core/tests/Drupal/Tests/Core/Common/AttributesTest.php b/core/tests/Drupal/Tests/Core/Common/AttributesTest.php
index 5bc5ab3..a354acd 100644
--- a/core/tests/Drupal/Tests/Core/Common/AttributesTest.php
+++ b/core/tests/Drupal/Tests/Core/Common/AttributesTest.php
@@ -40,7 +40,7 @@ public function providerTestAttributeData() {
       array(array('disabled' => FALSE), '', 'Boolean attribute is not rendered.'),
       // Verify empty attribute values are rendered.
       array(array('alt' => ''), ' alt=""', 'Empty attribute value #1.'),
-      array(array('alt' => NULL), ' alt=""', 'Empty attribute value #2.'),
+      array(array('alt' => NULL), '', 'Null attribute value #2.'),
       // Verify multiple attributes are rendered.
       array(
         array(
diff --git a/core/tests/Drupal/Tests/Core/Controller/ExceptionControllerTest.php b/core/tests/Drupal/Tests/Core/Controller/ExceptionControllerTest.php
index 7a0c238..d51d119 100644
--- a/core/tests/Drupal/Tests/Core/Controller/ExceptionControllerTest.php
+++ b/core/tests/Drupal/Tests/Core/Controller/ExceptionControllerTest.php
@@ -5,9 +5,8 @@
  * Contains \Drupal\Tests\Core\Controller\ExceptionControllerTest
  */
 
-namespace Drupal\Tests\Core\Controller;
+namespace Drupal\Tests\Core\Controller {
 
-use Drupal\Core\ContentNegotiation;
 use Drupal\Core\Controller\ExceptionController;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\HttpFoundation\Request;
@@ -36,10 +35,32 @@ public static function getInfo() {
   public function test405HTML() {
     $exception = new \Exception('Test exception');
     $flat_exception = FlattenException::create($exception, 405);
-    $exception_controller = new ExceptionController(new ContentNegotiation());
+    $translation_manager = $this->getStringTranslationStub();
+    $renderer = $this->getMock('Drupal\Core\Page\HtmlPageRendererInterface');
+    $title_resolver = $this->getMock('Drupal\Core\Controller\TitleResolverInterface');
+
+    $content_negotiation = $this->getMock('Drupal\Core\ContentNegotiation');
+    $content_negotiation->expects($this->any())
+      ->method('getContentType')
+      ->will($this->returnValue('html'));
+
+    $exception_controller = new ExceptionController($content_negotiation, $translation_manager, $title_resolver, $renderer);
     $response = $exception_controller->execute($flat_exception, new Request());
     $this->assertEquals($response->getStatusCode(), 405, 'HTTP status of response is correct.');
     $this->assertEquals($response->getContent(), 'Method Not Allowed', 'HTTP response body is correct.');
   }
 
 }
+
+}
+
+namespace {
+  use Drupal\Core\Language\Language;
+
+  if (!function_exists('language_default')) {
+    function language_default() {
+      $language = new Language(array('langcode' => 'en'));
+      return $language;
+    }
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Entity/Enhancer/EntityRouteEnhancerTest.php b/core/tests/Drupal/Tests/Core/Entity/Enhancer/EntityRouteEnhancerTest.php
index a029fa5..d98e0e9 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Enhancer/EntityRouteEnhancerTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Enhancer/EntityRouteEnhancerTest.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\Tests\Core\Entity\Enhancer;
 
-use Drupal\Core\ContentNegotiation;
 use Drupal\Core\Entity\Enhancer\EntityRouteEnhancer;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
@@ -34,12 +33,11 @@ public static function getInfo() {
    * @see \Drupal\Core\Entity\Enhancer\EntityRouteEnhancer::enhancer()
    */
   public function testEnhancer() {
-    $negotiation = $this->getMock('Drupal\core\ContentNegotiation', array('getContentType'));
-    $negotiation->expects($this->any())
-      ->method('getContentType')
-      ->will($this->returnValue('html'));
+    $controller_resolver = $this->getMock('Drupal\Core\Controller\ControllerResolverInterface');
+    $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
+    $form_builder = $this->getMock('Drupal\Core\Form\FormBuilderInterface');
 
-    $route_enhancer = new EntityRouteEnhancer($negotiation);
+    $route_enhancer = new EntityRouteEnhancer($controller_resolver, $entity_manager, $form_builder);
 
     // Set a controller to ensure it is not overridden.
     $request = new Request();
@@ -47,29 +45,32 @@ public function testEnhancer() {
     $defaults['_controller'] = 'Drupal\Tests\Core\Controller\TestController::content';
     $defaults['_entity_form'] = 'entity_test.default';
     $new_defaults = $route_enhancer->enhance($defaults, $request);
-    $this->assertEquals($defaults, $new_defaults, '_controller got overridden.');
+    $this->assertTrue(is_callable($new_defaults['_content']));
+    $this->assertInstanceOf('\Drupal\Core\Entity\HtmlEntityFormController', $new_defaults['_content'][0]);
+    $this->assertEquals($new_defaults['_content'][1], 'getContentResult');
+    $this->assertEquals($defaults['_controller'], $new_defaults['_controller'], '_controller got overridden.');
 
     // Set _entity_form and ensure that the form controller is set.
     $defaults = array();
     $defaults['_entity_form'] = 'entity_test.default';
-    $defaults = $route_enhancer->enhance($defaults, $request);
-    $this->assertEquals('\Drupal\Core\Entity\HtmlEntityFormController::content', $defaults['_controller'], 'The entity form controller was not set.');
+    $new_defaults = $route_enhancer->enhance($defaults, $request);
+    $this->assertTrue(is_callable($new_defaults['_content']));
+    $this->assertInstanceOf('\Drupal\Core\Entity\HtmlEntityFormController', $new_defaults['_content'][0]);
+    $this->assertEquals($new_defaults['_content'][1], 'getContentResult');
 
     // Set _entity_list and ensure that the entity list controller is set.
     $defaults = array();
     $defaults['_entity_list'] = 'entity_test.default';
-    $defaults = $route_enhancer->enhance($defaults, $request);
-    $this->assertEquals('controller.page:content', $defaults['_controller']);
-    $this->assertEquals('\Drupal\Core\Entity\Controller\EntityListController::listing', $defaults['_content'], 'The entity list controller was not set.');
-    $this->assertEquals('entity_test.default', $defaults['entity_type']);
-    $this->assertFalse(isset($defaults['_entity_list']));
+    $new_defaults = $route_enhancer->enhance($defaults, $request);
+    $this->assertEquals('\Drupal\Core\Entity\Controller\EntityListController::listing', $new_defaults['_content'], 'The entity list controller was not set.');
+    $this->assertEquals('entity_test.default', $new_defaults['entity_type']);
+    $this->assertFalse(isset($new_defaults['_entity_list']));
 
     // Set _entity_view and ensure that the entity view controller is set.
     $defaults = array();
     $defaults['_entity_view'] = 'entity_test.full';
     $defaults['entity_test'] = 'Mock entity';
     $defaults = $route_enhancer->enhance($defaults, $request);
-    $this->assertEquals('controller.page:content', $defaults['_controller']);
     $this->assertEquals('\Drupal\Core\Entity\Controller\EntityViewController::view', $defaults['_content'], 'The entity view controller was not set.');
     $this->assertEquals($defaults['_entity'], 'Mock entity');
     $this->assertEquals($defaults['view_mode'], 'full');
@@ -93,7 +94,6 @@ public function testEnhancer() {
 
     $defaults[RouteObjectInterface::ROUTE_OBJECT] = $route;
     $defaults = $route_enhancer->enhance($defaults, $request);
-    $this->assertEquals('controller.page:content', $defaults['_controller']);
     $this->assertEquals('\Drupal\Core\Entity\Controller\EntityViewController::view', $defaults['_content'], 'The entity view controller was not set.');
     $this->assertEquals($defaults['_entity'], 'Mock entity');
     $this->assertEquals($defaults['view_mode'], 'full');
@@ -104,7 +104,6 @@ public function testEnhancer() {
     $defaults['_entity_view'] = 'entity_test';
     $defaults['entity_test'] = 'Mock entity';
     $defaults = $route_enhancer->enhance($defaults, $request);
-    $this->assertEquals('controller.page:content', $defaults['_controller']);
     $this->assertEquals('\Drupal\Core\Entity\Controller\EntityViewController::view', $defaults['_content'], 'The entity view controller was not set.');
     $this->assertEquals($defaults['_entity'], 'Mock entity');
     $this->assertTrue(empty($defaults['view_mode']));
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityAccessCheckTest.php
index 3e6c3b9..bba4419 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityAccessCheckTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityAccessCheckTest.php
@@ -29,14 +29,6 @@ public static function getInfo() {
   }
 
   /**
-   * Tests the appliesTo method for the access checker.
-   */
-  public function testAppliesTo() {
-    $entity_access = new EntityAccessCheck();
-    $this->assertEquals($entity_access->appliesTo(), array('_entity_access'), 'Access checker returned the expected appliesTo() array.');
-  }
-
-  /**
    * Tests the method for checking access to routes.
    */
   public function testAccess() {
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityCreateAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityCreateAccessCheckTest.php
index dcda69a..fdf5675 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityCreateAccessCheckTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityCreateAccessCheckTest.php
@@ -45,15 +45,6 @@ protected function setUp() {
   }
 
   /**
-   * Tests the appliesTo method for the access checker.
-   */
-  public function testAppliesTo() {
-    $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
-
-    $entity_access = new EntityCreateAccessCheck($entity_manager);
-    $this->assertEquals($entity_access->appliesTo(), array('_entity_create_access'), 'Access checker returned the expected appliesTo() array.');
-  }
-  /**
    * Provides test data for testAccess.
    *
    * @return array
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityListControllerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityListControllerTest.php
index c8b07d6..6db04b5 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityListControllerTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityListControllerTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Tests\Core\Entity;
 
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\entity_test\EntityTestListController;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -21,7 +23,7 @@ class EntityListControllerTest extends UnitTestCase {
   /**
    * The entity used to construct the EntityListController.
    *
-   * @var \Drupal\user\Entity\Role
+   * @var \Drupal\user\RoleInterface|\PHPUnit_Framework_MockObject_MockObject
    */
   protected $role;
 
@@ -41,49 +43,16 @@ public static function getInfo() {
   }
 
   /**
-   * Entity info used by the test.
-   *
-   * @var array
-   *
-   * @see entity_get_info()
-   */
-  public static $entityInfo = array(
-    'entity_keys' => array(
-      'id' => 'id',
-      'label' => 'label',
-    ),
-    'config_prefix' => 'user.role',
-    'class' => 'Drupal\user\Entity\Role',
-  );
-
-
-  /**
    * {@inheritdoc}
    */
   protected function setUp() {
     parent::setUp();
 
-    $this->role = $this
-      ->getMockBuilder('Drupal\user\Entity\Role')
-      ->setConstructorArgs(array('entityInfo' => static::$entityInfo, 'user_role'))
-      ->getMock();
-
-    // Creates a stub role storage controller and replace the buildOperations()
-    // method with an empty version, because buildOperations() relies on hooks.
-    $role_storage_controller = $this->getMockBuilder('Drupal\user\RoleStorageController')
-      ->disableOriginalConstructor()
-      ->getMock();
-
-    $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
-      ->disableOriginalConstructor()
-      ->getMock();
-
-    $this->entityListController = $this->getMock('Drupal\entity_test\EntityTestListController', array('buildOperations'), array('user_role', static::$entityInfo, $role_storage_controller, $module_handler));
-
-    $this->entityListController->expects($this->any())
-      ->method('buildOperations')
-      ->will($this->returnValue(array()));
-
+    $this->role = $this->getMock('Drupal\user\RoleInterface');
+    $role_storage_controller = $this->getMock('Drupal\user\RoleStorageControllerInterface');
+    $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
+    $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+    $this->entityListController = new TestEntityListController($entity_type, $role_storage_controller, $module_handler);
   }
 
   /**
@@ -146,3 +115,8 @@ public function providerTestBuildRow() {
 
 }
 
+class TestEntityListController extends EntityTestListController {
+  public function buildOperations(EntityInterface $entity) {
+    return array();
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php
new file mode 100644
index 0000000..8624838
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Entity\EntityTypeTest.
+ */
+
+namespace Drupal\Tests\Core\Entity;
+
+use Drupal\Core\Entity\EntityType;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the \Drupal\Core\Entity\EntityType class.
+ *
+ * @group Drupal
+ * @group Entity
+ */
+class EntityTypeTest extends UnitTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Entity type test',
+      'description' => 'Unit test entity type info.',
+      'group' => 'Entity',
+    );
+  }
+
+  /**
+   * Sets up an EntityType object for a given set of values.
+   *
+   * @param array $definition
+   *   An array of values to use for the EntityType.
+   *
+   * @return \Drupal\Core\Entity\EntityTypeInterface
+   */
+  protected function setUpEntityType($definition) {
+    return new EntityType($definition);
+  }
+
+  /**
+   * Tests the getKeys() method.
+   *
+   * @dataProvider providerTestGetKeys
+   */
+  public function testGetKeys($entity_keys, $expected) {
+    $entity_type = $this->setUpEntityType(array('entity_keys' => $entity_keys));
+    $this->assertSame($expected, $entity_type->getKeys());
+  }
+
+  /**
+   * Tests the getKey() method.
+   *
+   * @dataProvider providerTestGetKeys
+   */
+  public function testGetKey($entity_keys, $expected) {
+    $entity_type = $this->setUpEntityType(array('entity_keys' => $entity_keys));
+    $this->assertSame($expected['bundle'], $entity_type->getKey('bundle'));
+    $this->assertSame(FALSE, $entity_type->getKey('bananas'));
+  }
+
+  /**
+   * Tests the hasKey() method.
+   *
+   * @dataProvider providerTestGetKeys
+   */
+  public function testHasKey($entity_keys, $expected) {
+    $entity_type = $this->setUpEntityType(array('entity_keys' => $entity_keys));
+    $this->assertSame(!empty($expected['bundle']), $entity_type->hasKey('bundle'));
+    $this->assertSame(!empty($expected['id']), $entity_type->hasKey('id'));
+    $this->assertSame(FALSE, $entity_type->hasKey('bananas'));
+  }
+
+  /**
+   * Provides test data.
+   */
+  public function providerTestGetKeys() {
+    return array(
+      array(array(), array('revision' => '', 'bundle' => '')),
+      array(array('id' => 'id'), array('id' => 'id', 'revision' => '', 'bundle' => '')),
+      array(array('bundle' => 'bundle'), array('bundle' => 'bundle', 'revision' => '')),
+    );
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
index f9a6718..777920a 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
@@ -25,7 +25,7 @@ class FormBuilderTest extends UnitTestCase {
   /**
    * The form builder being tested.
    *
-   * @var \Drupal\Core\Form\FormBuilderInterface
+   * @var \Drupal\Core\Form\FormBuilder
    */
   protected $formBuilder;
 
@@ -354,19 +354,59 @@ public function testGetFormWithObject() {
   }
 
   /**
-   * Tests the buildForm() method with a form object.
+   * Tests the getForm() method with a class name based form ID.
    */
-  public function testBuildFormWithObject() {
+  public function testGetFormWithClassString() {
+    $form_id = '\Drupal\Tests\Core\Form\TestForm';
+    $object = new TestForm();
+    $form = array();
+    $form_state = array();
+    $expected_form = $object->buildForm($form, $form_state);
+
+    $form = $this->formBuilder->getForm($form_id);
+    $this->assertFormElement($expected_form, $form, 'test');
+    $this->assertSame('test_form', $form['#id']);
+  }
+
+  /**
+   * Tests the buildForm() method with a string based form ID.
+   */
+  public function testBuildFormWithString() {
     $form_id = 'test_form_id';
     $expected_form = $form_id();
 
-    $form_arg = $this->getMockForm(NULL, $expected_form);
+    $form = $this->formBuilder->getForm($form_id);
+    $this->assertFormElement($expected_form, $form, 'test');
+    $this->assertSame($form_id, $form['#id']);
+  }
 
-    $form_state['build_info']['callback_object'] = $form_arg;
-    $form_state['build_info']['args'] = array();
+  /**
+   * Tests the buildForm() method with a class name based form ID.
+   */
+  public function testBuildFormWithClassString() {
+    $form_id = '\Drupal\Tests\Core\Form\TestForm';
+    $object = new TestForm();
+    $form = array();
+    $form_state = array();
+    $expected_form = $object->buildForm($form, $form_state);
 
     $form = $this->formBuilder->buildForm($form_id, $form_state);
     $this->assertFormElement($expected_form, $form, 'test');
+    $this->assertSame('test_form', $form['#id']);
+  }
+
+  /**
+   * Tests the buildForm() method with a form object.
+   */
+  public function testBuildFormWithObject() {
+    $form_id = 'test_form_id';
+    $expected_form = $form_id();
+
+    $form_arg = $this->getMockForm($form_id, $expected_form);
+
+    $form_state = array();
+    $form = $this->formBuilder->buildForm($form_arg, $form_state);
+    $this->assertFormElement($expected_form, $form, 'test');
     $this->assertSame($form_id, $form_state['build_info']['form_id']);
     $this->assertSame($form_id, $form['#id']);
   }
@@ -388,8 +428,7 @@ public function testBuildFormWithHookForms() {
         ),
       )));
 
-    $form_state['build_info']['args'] = array();
-
+    $form_state = array();
     $form = $this->formBuilder->buildForm($form_id, $form_state);
     $this->assertFormElement($expected_form, $form, 'test');
     $this->assertSame($form_id, $form_state['build_info']['form_id']);
@@ -405,13 +444,17 @@ public function testRebuildForm() {
     $expected_form = $form_id();
 
     // The form will be built four times.
-    $form_arg = $this->getMockForm(NULL, $expected_form, 4);
+    $form_arg = $this->getMock('Drupal\Core\Form\FormInterface');
+    $form_arg->expects($this->exactly(2))
+      ->method('getFormId')
+      ->will($this->returnValue($form_id));
+    $form_arg->expects($this->exactly(4))
+      ->method('buildForm')
+      ->will($this->returnValue($expected_form));
 
     // Do an initial build of the form and track the build ID.
     $form_state = array();
-    $form_state['build_info']['callback_object'] = $form_arg;
-    $form_state['build_info']['args'] = array();
-    $form = $this->formBuilder->buildForm($form_id, $form_state);
+    $form = $this->formBuilder->buildForm($form_arg, $form_state);
     $original_build_id = $form['#build_id'];
 
     // Rebuild the form, and assert that the build ID has not changed.
@@ -423,7 +466,7 @@ public function testRebuildForm() {
 
     // Rebuild the form again, and assert that there is a new build ID.
     $form_state['rebuild_info'] = array();
-    $form = $this->formBuilder->buildForm($form_id, $form_state);
+    $form = $this->formBuilder->buildForm($form_arg, $form_state);
     $this->assertNotSame($original_build_id, $form['#build_id']);
   }
 
@@ -615,52 +658,54 @@ public function providerTestGetError() {
   public function testGetCache() {
     $form_id = 'test_form_id';
     $expected_form = $form_id();
+    $expected_form['#token'] = FALSE;
 
-    // FormBuilder::buildForm() will be called 3 times, but the form object will
-    // only be called twice due to caching.
-    $form_arg = $this->getMockForm(NULL, $expected_form, 2);
+    // FormBuilder::buildForm() will be called twice, but the form object will
+    // only be called once due to caching.
+    $form_arg = $this->getMockForm($form_id, $expected_form, 1);
 
-    // The CSRF token and the user authentication are checked each time.
-    $this->csrfToken->expects($this->exactly(3))
+    // The CSRF token is checked each time.
+    $this->csrfToken->expects($this->exactly(2))
       ->method('get')
       ->will($this->returnValue('csrf_token'));
+    // The CSRF token is validated only when retrieving from the cache.
+    $this->csrfToken->expects($this->once())
+      ->method('validate')
+      ->with('csrf_token')
+      ->will($this->returnValue(TRUE));
+    // The user is checked for authentication once for the form building and
+    // twice for each cache set.
     $this->account->expects($this->exactly(3))
       ->method('isAuthenticated')
       ->will($this->returnValue(TRUE));
 
     // Do an initial build of the form and track the build ID.
     $form_state = array();
-    $form_state['build_info']['callback_object'] = $form_arg;
     $form_state['build_info']['args'] = array();
     $form_state['build_info']['files'] = array(array('module' => 'node', 'type' => 'pages.inc'));
     $form_state['cache'] = TRUE;
-    $form = $this->formBuilder->buildForm($form_id, $form_state);
-
-    // Rebuild the form, this time setting it up to be cached.
-    $form_state['rebuild'] = TRUE;
-    $form_state['rebuild_info']['copy']['#build_id'] = TRUE;
-    $form_state['input']['form_token'] = $form['#token'];
-    $form_state['input']['form_id'] = $form_id;
-    $form_state['input']['form_build_id'] = $form['#build_id'];
-    $form = $this->formBuilder->buildForm($form_id, $form_state);
+    $form = $this->formBuilder->buildForm($form_arg, $form_state);
 
     $cached_form = $form;
     $cached_form['#cache_token'] = 'csrf_token';
     // The form cache, form_state cache, and CSRF token validation will only be
     // called on the cached form.
     $this->formCache->expects($this->once())
+      ->method('setWithExpire');
+    $this->formCache->expects($this->once())
       ->method('get')
       ->will($this->returnValue($cached_form));
     $this->formStateCache->expects($this->once())
       ->method('get')
       ->will($this->returnValue($form_state));
-    $this->csrfToken->expects($this->once())
-      ->method('validate')
-      ->will($this->returnValue(TRUE));
 
     // The final form build will not trigger any actual form building, but will
     // use the form cache.
+    $form_state['input']['form_id'] = $form_id;
+    $form_state['input']['form_build_id'] = $form['#build_id'];
     $this->formBuilder->buildForm($form_id, $form_state);
+    $errors = $this->formBuilder->getErrors($form_state);
+    $this->assertEmpty($errors);
   }
 
   /**
@@ -677,13 +722,11 @@ public function testSendResponse() {
       ->method('prepare')
       ->will($this->returnValue($expected_form));
 
-    $form_arg = $this->getMockForm(NULL, $expected_form);
+    $form_arg = $this->getMockForm($form_id, $expected_form);
 
     // Do an initial build of the form and track the build ID.
     $form_state = array();
-    $form_state['build_info']['callback_object'] = $form_arg;
-    $form_state['build_info']['args'] = array();
-    $this->formBuilder->buildForm($form_id, $form_state);
+    $this->formBuilder->buildForm($form_arg, $form_state);
   }
 
   /**
@@ -691,7 +734,7 @@ public function testSendResponse() {
    *
    * @param string $form_id
    *   (optional) The form ID to be used. If none is provided, the form will be
-   *   set to expect that getFormId() will never be called.
+   *   set with no expectation about getFormId().
    * @param mixed $expected_form
    *   (optional) If provided, the expected form response for buildForm() to
    *   return. Defaults to NULL.
@@ -702,17 +745,11 @@ public function testSendResponse() {
    * @return \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Form\FormInterface
    *   The mocked form object.
    */
-  protected function getMockForm($form_id = NULL, $expected_form = NULL, $count = 1) {
+  protected function getMockForm($form_id, $expected_form = NULL, $count = 1) {
     $form = $this->getMock('Drupal\Core\Form\FormInterface');
-    if ($form_id) {
-      $form->expects($this->once())
-        ->method('getFormId')
-        ->will($this->returnValue($form_id));
-    }
-    else {
-      $form->expects($this->never())
-        ->method('getFormId');
-    }
+    $form->expects($this->once())
+      ->method('getFormId')
+      ->will($this->returnValue($form_id));
 
     if ($expected_form) {
       $form->expects($this->exactly($count))
@@ -840,7 +877,9 @@ public function getFormId() {
     return 'test_form';
   }
 
-  public function buildForm(array $form, array &$form_state) { }
+  public function buildForm(array $form, array &$form_state) {
+    return test_form_id();
+  }
   public function validateForm(array &$form, array &$form_state) { }
   public function submitForm(array &$form, array &$form_state) { }
 }
diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php
index e708c6a..6fe62a1 100644
--- a/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php
@@ -193,19 +193,21 @@ public function providerTestGetWeight() {
     return array(
       // Manually specify a weight, so this is used.
       array(array('weight' => 314), 'test_id', 314),
-      // Ensure that a default tab get a lower weight.
+      // Ensure that a default tab gets a lower weight.
       array(
         array(
-          'tab_root_id' => 'local_task_default',
+          'base_route' => 'local_task_default',
+          'route_name' => 'local_task_default',
           'id' => 'local_task_default'
         ),
         'local_task_default',
         -10
       ),
-      // If the root ID is different to the ID of the tab, ignore it.
+      // If the base route is different from the route of the tab, ignore it.
       array(
         array(
-          'tab_root_id' => 'local_task_example',
+          'base_route' => 'local_task_example',
+          'route_name' => 'local_task_other',
           'id' => 'local_task_default'
         ),
         'local_task_default',
@@ -214,8 +216,9 @@ public function providerTestGetWeight() {
       // Ensure that a default tab of a derivative gets the default value.
       array(
         array(
-          'tab_root_id' => 'local_task_derivative_default:example_id',
-          'id' => 'local_task_derivative_default'
+          'base_route' => 'local_task_example',
+          'id' => 'local_task_derivative_default:example_id',
+          'route_name' => 'local_task_example',
         ),
         'local_task_derivative_default:example_id',
         -10,
diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php
index 049e3ee..da5f90d 100644
--- a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php
@@ -122,13 +122,31 @@ public function testGetLocalTasksForRouteSingleLevelTitle() {
 
     $local_tasks = $this->manager->getLocalTasksForRoute('menu_local_task_test_tasks_view');
 
-    $result = array(
-      0 => array(
-        'menu_local_task_test_tasks_settings' => $mock_plugin,
-        'menu_local_task_test_tasks_view' => $mock_plugin,
-        'menu_local_task_test_tasks_edit' => $mock_plugin,
-      )
-    );
+    $result = $this->getLocalTasksForRouteResult($mock_plugin);
+
+    $this->assertEquals($result, $local_tasks);
+  }
+
+  /**
+   * Tests the getLocalTasksForRoute method on a child.
+   *
+   * @see \Drupal\system\Plugin\Type\MenuLocalTaskManager::getLocalTasksForRoute()
+   */
+  public function testGetLocalTasksForRouteForChild() {
+    $definitions = $this->getLocalTaskFixtures();
+
+    $this->pluginDiscovery->expects($this->once())
+      ->method('getDefinitions')
+      ->will($this->returnValue($definitions));
+
+    $mock_plugin = $this->getMock('Drupal\Core\Menu\LocalTaskInterface');
+
+    $this->setupFactory($mock_plugin);
+    $this->setupLocalTaskManager();
+
+    $local_tasks = $this->manager->getLocalTasksForRoute('menu_local_task_test_tasks_child1_page');
+
+    $result = $this->getLocalTasksForRouteResult($mock_plugin);
 
     $this->assertEquals($result, $local_tasks);
   }
@@ -266,33 +284,45 @@ protected function setupLocalTaskManager() {
   protected function getLocalTaskFixtures() {
     $definitions = array();
     $definitions['menu_local_task_test_tasks_settings'] = array(
-      'id' => 'menu_local_task_test_tasks_settings',
       'route_name' => 'menu_local_task_test_tasks_settings',
       'title' => 'Settings',
-      'tab_root_id' => 'menu_local_task_test_tasks_view',
+      'base_route' => 'menu_local_task_test_tasks_view',
     );
     $definitions['menu_local_task_test_tasks_edit'] = array(
-      'id' => 'menu_local_task_test_tasks_edit',
       'route_name' => 'menu_local_task_test_tasks_edit',
       'title' => 'Settings',
-      'tab_root_id' => 'menu_local_task_test_tasks_view',
+      'base_route' => 'menu_local_task_test_tasks_view',
       'weight' => 20,
     );
-    $definitions['menu_local_task_test_tasks_view'] = array(
-      'id' => 'menu_local_task_test_tasks_view',
+    // Make this ID different from the route name to catch code that
+    // confuses them.
+    $definitions['menu_local_task_test_tasks_view.tab'] = array(
       'route_name' => 'menu_local_task_test_tasks_view',
       'title' => 'Settings',
-      'tab_root_id' => 'menu_local_task_test_tasks_view',
+      'base_route' => 'menu_local_task_test_tasks_view',
     );
-    // Add the defaults from the LocalTaskManager.
+
+    $definitions['menu_local_task_test_tasks_view_child1'] = array(
+      'route_name' => 'menu_local_task_test_tasks_child1_page',
+      'title' => 'Settings child #1',
+      'parent_id' => 'menu_local_task_test_tasks_view.tab',
+    );
+    $definitions['menu_local_task_test_tasks_view_child2'] = array(
+      'route_name' => 'menu_local_task_test_tasks_child2_page',
+      'title' => 'Settings child #2',
+      'parent_id' => 'menu_local_task_test_tasks_view.tab',
+      'base_route' => 'this_should_be_replaced',
+    );
+    // Add the ID and defaults from the LocalTaskManager.
     foreach ($definitions as $id => &$info) {
+      $info['id'] = $id;
       $info += array(
         'id' => '',
         'route_name' => '',
         'route_parameters' => array(),
         'title' => '',
-        'tab_root_id' => '',
-        'tab_parent_id' => NULL,
+        'base_route' => '',
+        'parent_id' => NULL,
         'weight' => 0,
         'options' => array(),
         'class' => 'Drupal\Core\Menu\LocalTaskDefault',
@@ -308,11 +338,10 @@ protected function getLocalTaskFixtures() {
    *   The mock plugin.
    */
   protected function setupFactory($mock_plugin) {
-    $map = array(
-      array('menu_local_task_test_tasks_settings', array(), $mock_plugin),
-      array('menu_local_task_test_tasks_edit', array(), $mock_plugin),
-      array('menu_local_task_test_tasks_view', array(), $mock_plugin),
-    );
+    $map = array();
+    foreach ($this->getLocalTaskFixtures() as $info) {
+      $map[] = array($info['id'], array(), $mock_plugin);
+    }
     $this->factory->expects($this->any())
       ->method('createInstance')
       ->will($this->returnValueMap($map));
@@ -325,15 +354,19 @@ protected function setupFactory($mock_plugin) {
    *   The mock plugin.
    *
    * @return array
-   *   The expected result, keyed by local task leve.
+   *   The expected result, keyed by local task level.
    */
   protected function getLocalTasksForRouteResult($mock_plugin) {
     $result = array(
       0 => array(
         'menu_local_task_test_tasks_settings' => $mock_plugin,
-        'menu_local_task_test_tasks_view' => $mock_plugin,
+        'menu_local_task_test_tasks_view.tab' => $mock_plugin,
         'menu_local_task_test_tasks_edit' => $mock_plugin,
-      )
+      ),
+      1 => array(
+        'menu_local_task_test_tasks_view_child1' => $mock_plugin,
+        'menu_local_task_test_tasks_view_child2' => $mock_plugin,
+      ),
     );
     return $result;
   }
@@ -344,16 +377,26 @@ protected function getLocalTasksForRouteResult($mock_plugin) {
    * @return array
    */
   protected function getLocalTasksCache() {
+    $local_task_fixtures = $this->getLocalTaskFixtures();
     return array(
-      'tab_root_ids' => array(
+      'base_routes' => array(
         'menu_local_task_test_tasks_view' => 'menu_local_task_test_tasks_view',
       ),
       'parents' => array(
-        'menu_local_task_test_tasks_view' => 1,
+        'menu_local_task_test_tasks_view.tab' => TRUE,
       ),
       'children' => array(
-        '> menu_local_task_test_tasks_view' => $this->getLocalTaskFixtures(),
-      )
+        '> menu_local_task_test_tasks_view' => array(
+          'menu_local_task_test_tasks_settings' => $local_task_fixtures['menu_local_task_test_tasks_settings'],
+          'menu_local_task_test_tasks_edit' => $local_task_fixtures['menu_local_task_test_tasks_edit'],
+          'menu_local_task_test_tasks_view.tab' => $local_task_fixtures['menu_local_task_test_tasks_view.tab'],
+        ),
+        'menu_local_task_test_tasks_view.tab' => array(
+          // The manager will fill in the base_route before caching.
+          'menu_local_task_test_tasks_view_child1' => array('base_route' => 'menu_local_task_test_tasks_view') + $local_task_fixtures['menu_local_task_test_tasks_view_child1'],
+          'menu_local_task_test_tasks_view_child2' => array('base_route' => 'menu_local_task_test_tasks_view') + $local_task_fixtures['menu_local_task_test_tasks_view_child2'],
+        ),
+      ),
     );
   }
 
diff --git a/core/themes/bartik/bartik.theme b/core/themes/bartik/bartik.theme
index e334125..ab4683c 100644
--- a/core/themes/bartik/bartik.theme
+++ b/core/themes/bartik/bartik.theme
@@ -8,49 +8,51 @@
 use Drupal\Core\Template\RenderWrapper;
 
 /**
- * Implements hook_preprocess_HOOK() for HTML document templates.
+ * Implements hook_preprocess_HOOK() for page templates.
  *
  * Adds body classes if certain regions have content.
  */
-function bartik_preprocess_html(&$variables) {
+function bartik_preprocess_page(&$variables) {
   // Add information about the number of sidebars.
+  /** @var \Drupal\Core\Page\HtmlPage $page_object */
+  $page_object = $variables['page']['#page'];
+  $attributes = $page_object->getBodyAttributes();
+  $classes = $attributes['class'];
   if (!empty($variables['page']['sidebar_first']) && !empty($variables['page']['sidebar_second'])) {
-    $variables['attributes']['class'][] = 'two-sidebars';
+    $classes[] = 'two-sidebars';
   }
   elseif (!empty($variables['page']['sidebar_first'])) {
-    $variables['attributes']['class'][] = 'one-sidebar';
-    $variables['attributes']['class'][] = 'sidebar-first';
+    $classes[] = 'one-sidebar';
+    $classes[] = 'sidebar-first';
   }
   elseif (!empty($variables['page']['sidebar_second'])) {
-    $variables['attributes']['class'][] = 'one-sidebar';
-    $variables['attributes']['class'][] = 'sidebar-second';
+    $classes[] = 'one-sidebar';
+    $classes[] = 'sidebar-second';
   }
   else {
-    $variables['attributes']['class'][] = 'no-sidebars';
+    $classes[] = 'no-sidebars';
   }
 
   if (!empty($variables['page']['featured'])) {
-    $variables['attributes']['class'][] = 'featured';
+    $classes[] = 'featured';
   }
 
   if (!empty($variables['page']['triptych_first'])
     || !empty($variables['page']['triptych_middle'])
     || !empty($variables['page']['triptych_last'])) {
-    $variables['attributes']['class'][] = 'triptych';
+    $classes[] = 'triptych';
   }
 
   if (!empty($variables['page']['footer_firstcolumn'])
     || !empty($variables['page']['footer_secondcolumn'])
     || !empty($variables['page']['footer_thirdcolumn'])
     || !empty($variables['page']['footer_fourthcolumn'])) {
-    $variables['attributes']['class'][] = 'footer-columns';
+    $classes[] = 'footer-columns';
   }
-}
 
-/**
- * Implements hook_preprocess_HOOK() for page templates.
- */
-function bartik_preprocess_page(&$variables) {
+  // Store back the classes to the htmlpage object.
+  $attributes['class'] = $classes;
+
   // Pass the main menu and secondary menu to the template as render arrays.
   if (!empty($variables['main_menu'])) {
     $variables['main_menu']['#attributes']['id'] = 'main-menu-links';
diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme
index 7defd49..fa99324 100644
--- a/core/themes/seven/seven.theme
+++ b/core/themes/seven/seven.theme
@@ -31,23 +31,24 @@ function seven_library_info() {
 }
 
 /**
- * Implements hook_preprocess_HOOK() for HTML document templates.
+ * Implements hook_preprocess_HOOK() for page templates.
  */
-function seven_preprocess_html(&$variables) {
+function seven_preprocess_page(&$variables) {
+  /** @var \Drupal\Core\Page\HtmlPage $page_object */
+  $page_object = $variables['page']['#page'];
+  $attributes = $page_object->getBodyAttributes();
+  $classes = $attributes['class'];
   // Add information about the number of sidebars.
+
   if (!empty($variables['page']['sidebar_first'])) {
-    $variables['attributes']['class'][] = 'one-sidebar';
-    $variables['attributes']['class'][] = 'sidebar-first';
+    $classes[] = 'one-sidebar';
+    $classes[] = 'sidebar-first';
   }
   else {
-    $variables['attributes']['class'][] = 'no-sidebars';
+    $classes[] = 'no-sidebars';
   }
-}
+  $attributes['class'] = $classes;
 
-/**
- * Implements hook_preprocess_HOOK() for page templates.
- */
-function seven_preprocess_page(&$variables) {
   $variables['primary_local_tasks'] = $variables['tabs'];
   unset($variables['primary_local_tasks']['#secondary']);
   $variables['secondary_local_tasks'] = array(
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index b3c54b3..5a46b12 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -246,14 +246,8 @@
  * Example:
  * @code
  *   $config_directories = array(
- *     CONFIG_ACTIVE_DIRECTORY => array(
- *       'path' => '/some/directory/outside/webroot',
- *       'absolute' => TRUE,
- *     ),
- *     CONFIG_STAGING_DIRECTORY => array(
- *       'path' => '/another/directory/outside/webroot',
- *       'absolute' => TRUE,
- *     ),
+ *     CONFIG_ACTIVE_DIRECTORY => '/some/directory/outside/webroot',
+ *     CONFIG_STAGING_DIRECTORY => '/another/directory/outside/webroot',
  *   );
  * @endcode
  */
