diff --git a/core/core.services.yml b/core/core.services.yml
index 65882af..d51e5c4 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -343,12 +343,12 @@ services:
       - { name: legacy_route_enhancer, priority: 20 }
   route_enhancer.entity:
     class: Drupal\Core\Entity\Enhancer\EntityRouteEnhancer
-    arguments: ['@content_negotiation']
+    arguments: ['@controller_resolver', '@plugin.manager.entity']
     tags:
       - { name: route_enhancer, priority: 15 }
   route_enhancer.form:
     class: Drupal\Core\Routing\Enhancer\FormEnhancer
-    arguments: ['@content_negotiation']
+    arguments: ['@service_container', '@controller_resolver']
     tags:
       - { name: route_enhancer, priority: 10 }
   route_special_attributes_subscriber:
@@ -357,10 +357,19 @@ services:
       - { name: event_subscriber }
   controller.page:
     class: Drupal\Core\Controller\HtmlPageController
-    arguments: ['@http_kernel', '@controller_resolver']
+    arguments: ['@controller_resolver', '@language_manager']
+  controller.ajax:
+    class: Drupal\Core\Controller\AjaxController
+    arguments: ['@controller_resolver']
+  controller.form:
+    class: Drupal\Core\Controller\FormController
+    arguments: ['@controller_resolver', '@service_container']
+  controller.entityform:
+    class: Drupal\Core\Entity\HtmlEntityFormController
+    arguments: ['@controller_resolver', '@service_container', '@entity.manager']
   controller.dialog:
     class: Drupal\Core\Controller\DialogController
-    arguments: ['@http_kernel']
+    arguments: ['@controller_resolver']
   router_listener:
     class: Symfony\Component\HttpKernel\EventListener\RouterListener
     tags:
@@ -463,7 +472,7 @@ services:
     arguments: ['@language_manager', '@string_translation']
   exception_controller:
     class: Drupal\Core\Controller\ExceptionController
-    arguments: ['@content_negotiation']
+    arguments: ['@content_negotiation', '@language_manager']
     calls:
       - [setContainer, ['@service_container']]
   exception_listener:
diff --git a/core/includes/common.inc b/core/includes/common.inc
index d6886f2..dd04e5d 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -369,12 +369,23 @@ function _drupal_default_html_head() {
 }
 
 /**
- * Retrieves output to be displayed in the HEAD tag of the HTML page.
+ * Retrieves output or array to be displayed in the HEAD tag of the HTML page.
+ *
+ * @param bool $render
+ *   Should the HTML head be rendered.
+ *
+ * @return string|array
+ *   Either the rendered or the structural data of the HTML head.
  */
-function drupal_get_html_head() {
+function drupal_get_html_head($render = TRUE) {
   $elements = drupal_add_html_head();
   drupal_alter('html_head', $elements);
-  return drupal_render($elements);
+  if ($render) {
+    return drupal_render($elements);
+  }
+  else {
+    return $elements;
+  }
 }
 
 /**
@@ -3645,7 +3656,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
@@ -3655,10 +3666,18 @@ function drupal_pre_render_dropbutton($element) {
  *   - #show_messages: Suppress drupal_get_message() items. Used by Batch
  *     API (optional).
  *
+ * @param bool $legacy
+ *   TRUE if this function should use the old-style page element that includes
+ *   the HTML wrapper. FALSE to use the page element that doesn't self-render
+ *   the HTML part of the page. Defaults to false.
+ *
+ * @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, $legacy = FALSE) {
   $main_content_display = &drupal_static('system_main_content_added', FALSE);
 
   // Pull out the page title to set it back later.
@@ -3671,7 +3690,7 @@ function drupal_render_page($page) {
   // in the page with defaults.
   if (is_string($page) || (is_array($page) && (!isset($page['#type']) || ($page['#type'] != 'page')))) {
     drupal_set_page_content($page);
-    $page = element_info('page');
+    $page = $legacy ? element_info('page') : element_info('page');
   }
 
   // Modules can add elements to $page as needed in hook_page_build().
@@ -3695,6 +3714,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, TRUE);
   return drupal_render($page);
 }
 
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 87b3b18..e582ea0 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -8,6 +8,7 @@
  * customized by user themes.
  */
 
+use Drupal\Component\Utility\SortArray;
 use Drupal\Component\Utility\String;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Config\Config;
@@ -19,6 +20,10 @@
 use Drupal\Core\Theme\ThemeSettings;
 use Drupal\Component\Utility\NestedArray;
 
+use Drupal\Core\Page\Link;
+use Drupal\Core\Page\Metatag;
+use Drupal\node\NodeInterface;
+
 /**
  * @defgroup content_flags Content markers
  * @{
@@ -2506,31 +2511,23 @@ 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->getAttributes();
+  $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';
-
-  // Add information about the number of sidebars.
-  if (!empty($variables['page']['sidebar_first']) && !empty($variables['page']['sidebar_second'])) {
-    $variables['attributes']['class'][] = 'two-sidebars';
-  }
-  elseif (!empty($variables['page']['sidebar_first'])) {
-    $variables['attributes']['class'][] = 'one-sidebar';
-    $variables['attributes']['class'][] = 'sidebar-first';
-  }
-  elseif (!empty($variables['page']['sidebar_second'])) {
-    $variables['attributes']['class'][] = 'one-sidebar';
-    $variables['attributes']['class'][] = 'sidebar-second';
-  }
-  else {
-    $variables['attributes']['class'][] = 'no-sidebars';
-  }
+  $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', '-')) {
@@ -2545,42 +2542,30 @@ function template_preprocess_html(&$variables) {
   }
 
   // If on an individual node page, add the node type to body classes.
-  if ($node = menu_get_object()) {
+  if (($node = menu_get_object()) && $node instanceof NodeInterface) {
     $variables['attributes']['class'][] = drupal_html_class('node-type-' . $node->getType());
   }
 
-  // Initializes attributes which are specific to the html and body elements.
-  $variables['html_attributes'] = new Attribute;
-
-  // HTML element attributes.
-  $variables['html_attributes']['lang'] = $language_interface->id;
-  $variables['html_attributes']['dir'] = $language_interface->direction ? 'rtl' : 'ltr';
-
   // Add favicon.
   if (theme_get_setting('features.favicon')) {
     $favicon = theme_get_setting('favicon.url');
     $type = theme_get_setting('favicon.mimetype');
-    drupal_add_html_head_link(array('rel' => 'shortcut icon', 'href' => drupal_strip_dangerous_protocols($favicon), 'type' => $type));
+    $link = new Link(drupal_strip_dangerous_protocols($favicon), 'shortcut icon');
+    $link->setAttribute('type', $type);
+    $page->addLink($link);
   }
 
   $site_config = Drupal::config('system.site');
-  // Construct page title.
-  if (isset($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')),
     );
   }
-  elseif (drupal_get_title()) {
-    $head_title = array(
-      'title' => strip_tags(drupal_get_title()),
-      'name' => check_plain($site_config->get('name')),
-    );
-  }
   else {
-    $head_title = array('name' => check_plain($site_config->get('name')));
+    $head_title = array('name' => String::checkPlain($site_config->get('name')));
     if ($site_config->get('slogan')) {
-      $head_title['slogan'] = strip_tags(filter_xss_admin($site_config->get('slogan')));
+      $head_title['slogan'] = strip_tags(Xss::filterAdmin($site_config->get('slogan')));
     }
   }
 
@@ -2588,6 +2573,12 @@ function template_preprocess_html(&$variables) {
   $variables['head_title'] = implode(' | ', $head_title);
 
   // Display the html.tpl.php's default mobile metatags for responsive design.
+  // @todo Add the metatags via drupal_add_html_head for now, so the alter hook
+  //   in drupal_get_html_head() still works.
+
+//  $page->addMetatag(new Metatag('MobileOptimized', 'width'));
+//  $page->addMetatag(new Metatag('HandheldFriendly', 'true'));
+//  $page->addMetatag(new Metatag('viewport', 'width=device-width'));
   $elements = array(
     'MobileOptimized' => array(
       '#tag' => 'meta',
@@ -2615,6 +2606,20 @@ function template_preprocess_html(&$variables) {
     drupal_add_html_head($element, $name);
   }
 
+  // @todo Decide whether we want to get drupal_get_html_head() support until
+  //   each instance is ported (drupal_add_html_head() and
+  // drupal_add_html_head_link() would be needed).
+  $html_heads = drupal_get_html_head(FALSE);
+  uasort($html_heads, 'Drupal\Component\Utility\SortArray::sortByWeightElement');
+  foreach ($html_heads as $name => $tag) {
+    if ($tag['#tag'] == 'link') {
+      $page->addLink(new Link($name, isset($tag['#attributes']['content']) ? $tag['#attributes']['content'] : '', $tag['#attributes']));
+    }
+    elseif ($tag['#tag'] == 'meta') {
+      $page->addMetatag(new Metatag($name, isset($tag['#attributes']['content']) ? $tag['#attributes']['content'] : '', $tag['#attributes']));
+    }
+  }
+
   // Populate the page template suggestions.
   if ($suggestions = theme_get_suggestions(arg(), 'html')) {
     $variables['theme_hook_suggestions'] = $suggestions;
@@ -2622,15 +2627,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->getContentTop());
+  $variables['page_bottom'][] = array('#markup' => $page->getContentBottom());
 
   // Add footer scripts as '#markup' so they can be rendered with other
   // elements in page_bottom.
@@ -2638,7 +2636,9 @@ function template_preprocess_html(&$variables) {
   $variables['page_bottom'][] = array('#markup' => $footer_scripts);
 
   // Wrap function calls in an object so they can be called when printed.
-  $variables['head'] = new RenderWrapper('drupal_get_html_head');
+  $variables['head'] = new RenderWrapper(function() use ($page) {
+    return implode("\n", $page->metatags()) . implode("\n", $page->links());
+  });
   $variables['styles'] = new RenderWrapper('drupal_get_css');
   $variables['scripts'] = new RenderWrapper('drupal_get_js');
 }
@@ -3014,12 +3014,13 @@ function drupal_common_theme() {
   return array(
     // From theme.inc.
     'html' => array(
-      'render element' => 'page',
+      'variables' => array('page_object' => NULL),
       'template' => 'html',
     ),
     'page' => array(
       'render element' => 'page',
       'template' => 'page',
+      'theme wrapper' => 'html',
     ),
     'region' => array(
       'render element' => 'elements',
@@ -3087,7 +3088,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/lib/Drupal/Core/Controller/AjaxController.php b/core/lib/Drupal/Core/Controller/AjaxController.php
index 7b2cece..410b1d0 100644
--- a/core/lib/Drupal/Core/Controller/AjaxController.php
+++ b/core/lib/Drupal/Core/Controller/AjaxController.php
@@ -10,8 +10,10 @@
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\InsertCommand;
 use Drupal\Core\Ajax\PrependCommand;
+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 +21,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 +49,64 @@ 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;
+    if ($content instanceof HtmlPage) {
+      $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');
+    // A page callback could return a render array or 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 array
+   *   The render array that results from invoking the controller.
+   */
+  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 cde2876..36c13e7 100644
--- a/core/lib/Drupal/Core/Controller/DialogController.php
+++ b/core/lib/Drupal/Core/Controller/DialogController.php
@@ -7,11 +7,13 @@
 
 namespace Drupal\Core\Controller;
 
-use Drupal\Core\Ajax\AjaxResponse;
-use Drupal\Core\Ajax\OpenDialogCommand;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\OpenDialogCommand;
+use Drupal\Core\Page\HtmlPage;
 
 /**
  * Defines a default controller for dialog requests.
@@ -19,20 +21,20 @@
 class DialogController {
 
   /**
-   * The HttpKernel object to use for subrequests.
+   * The controller resolver service.
    *
-   * @var \Symfony\Component\HttpKernel\HttpKernelInterface
+   * @var \Drupal\Core\Controller\ControllerResolver
    */
-  protected $httpKernel;
+  protected $controllerResolver;
 
   /**
    * Constructs a new DialogController.
    *
-   * @param \Symfony\Component\HttpKernel\HttpKernelInterface $kernel
-   *   The kernel.
+   * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
+   *   The controller resolver service.
    */
-  public function __construct(HttpKernelInterface $kernel) {
-    $this->httpKernel = $kernel;
+  public function __construct(ControllerResolverInterface $controller_resolver) {
+    $this->controllerResolver = $controller_resolver;
   }
 
   /**
@@ -75,8 +77,8 @@ protected function forward(Request $request) {
    * @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);
   }
 
   /**
@@ -90,45 +92,81 @@ public function modal(Request $request) {
    * @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();
-      // @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);
+
+    if ($page_content instanceof HtmlPage) {
+      $page_content = $page_content->getContent();
+    }
+    if ($page_content instanceof Response) {
+      $page_content = $page_content->getContent();
+    }
+
+    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.
+    $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 array
+   *   The render array that results from invoking the controller.
+   */
+  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 b03cc8e..6e5ba24 100644
--- a/core/lib/Drupal/Core/Controller/ExceptionController.php
+++ b/core/lib/Drupal/Core/Controller/ExceptionController.php
@@ -7,19 +7,23 @@
 
 namespace Drupal\Core\Controller;
 
+use Drupal\Core\Language\LanguageManager;
 use Symfony\Component\DependencyInjection\ContainerAware;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
 use Symfony\Component\HttpKernel\Exception\FlattenException;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 use Drupal\Core\ContentNegotiation;
+use Drupal\Core\Page\HtmlPage;
 
 /**
  * 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,13 +33,34 @@ class ExceptionController extends ContainerAware {
   protected $negotiation;
 
   /**
+   * The service container.
+   *
+   * @var ContainerInterface
+   */
+  protected $container;
+
+  /**
+   * Sets the Container associated with this Controller.
+   *
+   * @param ContainerInterface $container A ContainerInterface instance
+   *
+   * @api
+   */
+  public function setContainer(ContainerInterface $container = null) {
+    $this->container = $container;
+  }
+
+  /**
    * Constructor.
    *
-   * @param Drupal\Core\ContentNegotiation $negotiation
+   * @param \Drupal\Core\ContentNegotiation $negotiation
    *   The content negotiation library to use to determine the correct response
    *   format.
+   * @param \Drupal\Core\Language\LanguageManager $language_manager
+   *   The language manager.
    */
-  public function __construct(ContentNegotiation $negotiation) {
+  public function __construct(ContentNegotiation $negotiation, LanguageManager $language_manager) {
+    parent::__construct($language_manager);
     $this->negotiation = $negotiation;
   }
 
@@ -84,14 +109,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('/' . $path, 'get', array('destination' => $system_path), $request->cookies->all(), array(), $request->server->all());
+      $subrequest = Request::create('/' . $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
@@ -113,15 +139,18 @@ 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.'),
+      );
+
+      $page = $this->createHtmlPage($page_content, $request);
+      $page->setTitle(t('Access denied'));
+      $page->setStatusCode(403);
 
-      // @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);
+      $output = theme('html', array('page_object' => $page));
+      $response = new Response($output, $page->getStatusCode());
 
-      $response = new Response($content, 403);
+      return $response;
     }
 
     return $response;
@@ -164,7 +193,7 @@ 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('/' . $path, 'get', array(), $request->cookies->all(), array(), $request->server->all());
+      $subrequest = Request::create('/' . $path, 'get', array('_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
@@ -186,14 +215,16 @@ 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())),
+      );
+
+      $page = $this->createHtmlPage($page_content, $request);
+      $page->setTitle(t('Page not found'));
+      $page->setStatusCode(404);
+
+      $output = theme('html', array('page_object' => $page));
+      $response = new Response($output, $page->getStatusCode());
     }
 
     return $response;
@@ -252,16 +283,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..c99e8fc
--- /dev/null
+++ b/core/lib/Drupal/Core/Controller/FormController.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Controler\FormController.
+ */
+
+namespace Drupal\Core\Controller;
+
+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;
+
+  /**
+   * Constructs a new \Drupal\Core\Routing\Enhancer\FormEnhancer object.
+   */
+  public function __construct(ControllerResolverInterface $resolver) {
+    $this->resolver = $resolver;
+  }
+
+  /**
+   * Returns the result of invoking the form.
+   *
+   * @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.
+   */
+  public function getContentResult(Request $request) {
+    $form_object = $this->getFormObject($request, $this->formDefinition);
+
+    // 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->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 = _drupal_form_id($form_object, $form_state);
+    return drupal_build_form($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..a8e1b3a
--- /dev/null
+++ b/core/lib/Drupal/Core/Controller/HtmlControllerBase.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace Drupal\Core\Controller;
+
+use Drupal\Core\Language\LanguageManager;
+use Symfony\Component\HttpFoundation\Request;
+
+use Drupal\Core\Language\Language;
+use Drupal\Core\Page\HtmlPage;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Base class for Html page-generating controllers.
+ */
+class HtmlControllerBase {
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManager
+   */
+  protected $languageManager;
+
+  /**
+   * Constructs a new HtmlControllerBase object.
+   *
+   * @param \Drupal\Core\Language\LanguageManager $language_manager
+   *   The language manager.
+   */
+  public function __construct(LanguageManager $language_manager) {
+    $this->languageManager = $language_manager;
+  }
+
+  /**
+   * Converts a render array into an HtmlPage 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.
+   */
+  public function createHtmlPage($page_content, Request $request) {
+    if ($page_content instanceof HtmlPage || $page_content instanceof Response) {
+      return $page_content;
+    }
+
+    if (!is_array($page_content)) {
+      $page_content = array(
+        '#markup' => $page_content,
+      );
+    }
+
+    $page = new HtmlPage('', $request->attributes->get('_title'));
+
+    $page_array = drupal_prepare_page($page_content);
+
+    $page = $this->preparePage($page, $page_array);
+
+    $page->setContentTop(drupal_render($page_array['page_top']));
+    $page->setContentBottom(drupal_render($page_array['page_bottom']));
+    $page->setContent(drupal_render($page_array));
+
+    return $page;
+  }
+
+  /**
+   * 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.
+   */
+  protected function preparePage(HtmlPage $page, $page_array) {
+    if (isset($page_array['#title'])) {
+      $page->setTitle($page_array['#title']);
+      unset($page_array['#title']);
+    }
+    elseif ($title = drupal_get_title()) {
+      // drupal_set_title() already ensured security, so not letting the
+      // title passing would cause double escaping.
+      $page->setTitle($title, PASS_THROUGH);
+    }
+
+    $attributes = $page->getAttributes();
+
+    // Add information about the number of sidebars.
+    $classes = $attributes['class'];
+    if (!empty($page_array['sidebar_first']) && !empty($page_array['page']['sidebar_second'])) {
+      $classes[] = 'two-sidebars';
+    }
+    elseif (!empty($page_array['sidebar_first'])) {
+      $classes[] = 'one-sidebar';
+      $classes[] = 'sidebar-first';
+    }
+    elseif (!empty($page_array['sidebar_second'])) {
+      $classes[] = 'one-sidebar';
+      $classes[] = 'sidebar-second';
+    }
+    else {
+      $classes[] = 'no-sidebars';
+    }
+    $attributes['class'] = $classes;
+
+    // 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/Controller/HtmlFormController.php b/core/lib/Drupal/Core/Controller/HtmlFormController.php
index 8b3e70a..246611a 100644
--- a/core/lib/Drupal/Core/Controller/HtmlFormController.php
+++ b/core/lib/Drupal/Core/Controller/HtmlFormController.php
@@ -2,70 +2,39 @@
 
 /**
  * @file
- * Contains \Drupal\Core\Controller\HtmlFormController.
+ * Contains \Drupal\Core\Controler\HtmlFormController.
  */
 
 namespace Drupal\Core\Controller;
 
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\DependencyInjection\ContainerAwareInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use \Drupal\Core\Controller\ControllerResolverInterface;
 
-/**
- * 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.
+   * The service container.
    *
    * @var \Symfony\Component\DependencyInjection\ContainerInterface
    */
   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_id = _drupal_form_id($form_object, $form_state);
-    return drupal_build_form($form_id, $form_state);
+  public function __construct(ControllerResolverInterface $resolver, ContainerInterface $container, $class) {
+    parent::__construct($resolver);
+    $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 d87b3ed..e012733 100644
--- a/core/lib/Drupal/Core/Controller/HtmlPageController.php
+++ b/core/lib/Drupal/Core/Controller/HtmlPageController.php
@@ -7,21 +7,16 @@
 
 namespace Drupal\Core\Controller;
 
+use Drupal\Core\Language\LanguageManager;
 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,12 +28,13 @@ class HtmlPageController {
   /**
    * Constructs a new HtmlPageController.
    *
-   * @param \Symfony\Component\HttpKernel\HttpKernelInterface $kernel
    * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
    *   The controller resolver.
+   * @param \Drupal\Core\Language\LanguageManager $language_manager
+   *   The language manager.
    */
-  public function __construct(HttpKernelInterface $kernel, ControllerResolverInterface $controller_resolver) {
-    $this->httpKernel = $kernel;
+  public function __construct(ControllerResolverInterface $controller_resolver, LanguageManager $language_manager) {
+    parent::__construct($language_manager);
     $this->controllerResolver = $controller_resolver;
   }
 
@@ -54,24 +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(
-        '#markup' => $page_content,
-      );
+    $page_content = $this->getContentResult($request, $_content);
+    return $this->createHtmlPage($page_content, $request);
+  }
+
+  /**
+   * 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 array
+   *   The render array that results from invoking the controller.
+   */
+  public function getContentResult(Request $request, $controller_definition) {
+    if ($controller_definition instanceof \Closure) {
+      $callable = $controller_definition;
     }
-    // If no title was returned fall back to one defined in the route.
-    if (!isset($page_content['#title']) && $request->attributes->has('_title')) {
-      $page_content['#title'] = $request->attributes->get('_title');
+    else {
+      $callable = $this->controllerResolver->getControllerFromDefinition($controller_definition);
     }
+    $arguments = $this->controllerResolver->getArguments($request, $callable);
+    $page_content = call_user_func_array($callable, $arguments);
 
-    $response = new Response(drupal_render_page($page_content));
-    return $response;
+    return $page_content;
   }
 
 }
diff --git a/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php b/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php
index 2cf8d47..a3512c7 100644
--- a/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php
+++ b/core/lib/Drupal/Core/Entity/Enhancer/EntityRouteEnhancer.php
@@ -10,7 +10,9 @@
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Drupal\Core\ContentNegotiation;
+use Drupal\Core\Entity\HtmlEntityFormController;
+use Drupal\Core\Controller\ControllerResolverInterface;
+use Drupal\Core\Entity\EntityManager;
 
 /**
  * Enhances an entity form route with the appropriate controller.
@@ -18,38 +20,42 @@
 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\EntityManager
    */
-  public function __construct(ContentNegotiation $negotiation) {
-    $this->negotiation = $negotiation;
+  protected $manager;
+
+  /**
+   * Constructs a new EntityRouteEnhancer object.
+   */
+  public function __construct(ControllerResolverInterface $resolver, EntityManager $manager) {
+    $this->resolver = $resolver;
+    $this->manager = $manager;
   }
 
   /**
    * {@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, $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/HtmlEntityFormController.php b/core/lib/Drupal/Core/Entity/HtmlEntityFormController.php
index 86d8094..f1bec17 100644
--- a/core/lib/Drupal/Core/Entity/HtmlEntityFormController.php
+++ b/core/lib/Drupal/Core/Entity/HtmlEntityFormController.php
@@ -7,22 +7,31 @@
 
 namespace Drupal\Core\Entity;
 
-use Drupal\Core\Controller\HtmlFormController;
 use Symfony\Component\HttpFoundation\Request;
 
+use Drupal\Core\Controller\FormController;
+use Drupal\Core\Entity\EntityManager;
+use \Drupal\Core\Controller\ControllerResolverInterface;
+
 /**
  * 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.
    *
-   * Due to reflection, the argument must be named $_entity_form. The parent
-   * method has $request and $_form, but the parameter must match the route.
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $manager;
+
+  /**
+   * Constructs a new \Drupal\Core\Routing\Enhancer\FormEnhancer object.
    */
-  public function content(Request $request, $_entity_form) {
-    return parent::content($request, $_entity_form);
+  public function __construct(ControllerResolverInterface $resolver, EntityManager $manager, $form_definition) {
+    parent::__construct($resolver);
+    $this->manager = $manager;
+    $this->formDefinition = $form_definition;
   }
 
   /**
@@ -46,8 +55,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 +63,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/EventSubscriber/LegacyControllerSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php
index dca2708..5982943 100644
--- a/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php
@@ -7,10 +7,14 @@
 
 namespace Drupal\Core\EventSubscriber;
 
+use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\KernelEvents;
 use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
+use Drupal\Core\Language\Language;
+use Drupal\Core\Page\HtmlPage;
+
 /**
  * Access subscriber for controller requests.
  */
@@ -43,7 +47,58 @@ public function onKernelControllerLegacy(FilterControllerEvent $event) {
     if ($request->attributes->get('_legacy')) {
       $router_item = $request->attributes->get('_drupal_menu_item');
       $new_controller = function() use ($router_item) {
-        return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
+        $page_content = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
+
+        if ($page_content instanceof HtmlPage || $page_content instanceof Response) {
+          return $page_content;
+        }
+
+        $page = new HtmlPage();
+
+        $page_array = drupal_prepare_page($page_content);
+
+        //$page = $this->preparePage($page, $page_array);
+        if (isset($page_array['#title'])) {
+          $page->setTitle($page_array['#title']);
+          unset($page_array['#title']);
+        }
+        elseif ($title = drupal_get_title()) {
+          // drupal_set_title() already ensured security, so not letting the
+          // title passing would cause double escaping.
+          $page->setTitle($title, PASS_THROUGH);
+        }
+
+        $attributes = $page->getAttributes();
+
+        // Add information about the number of sidebars.
+        $classes = $attributes['class'];
+        if (!empty($page_array['sidebar_first']) && !empty($page_array['page']['sidebar_second'])) {
+          $classes[] = 'two-sidebars';
+        }
+        elseif (!empty($page_array['sidebar_first'])) {
+          $classes[] = 'one-sidebar';
+          $classes[] = 'sidebar-first';
+        }
+        elseif (!empty($page_array['sidebar_second'])) {
+          $classes[] = 'one-sidebar';
+          $classes[] = 'sidebar-second';
+        }
+        else {
+          $classes[] = 'no-sidebars';
+        }
+        $attributes['class'] = $classes;
+
+        // HTML element attributes.
+        $language_interface = language(Language::TYPE_INTERFACE);
+        $html_attributes = $page->getHtmlAttributes();
+        $html_attributes['lang'] = $language_interface->id;
+        $html_attributes['dir'] = $language_interface->direction ? 'rtl' : 'ltr';
+
+        $page->setContentTop(drupal_render($page_array['page_top']));
+        $page->setContentBottom(drupal_render($page_array['page_bottom']));
+        $page->setContent(drupal_render($page_array));
+
+        return $page;
       };
       $event->setController($new_controller);
     }
diff --git a/core/lib/Drupal/Core/EventSubscriber/LegacyRequestSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyRequestSubscriber.php
index 39828ef..603174c 100644
--- a/core/lib/Drupal/Core/EventSubscriber/LegacyRequestSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/LegacyRequestSubscriber.php
@@ -46,7 +46,7 @@ public function onKernelRequestLegacy(GetResponseEvent $event) {
    *   An array of event listener definitions.
    */
   static function getSubscribedEvents() {
-    $events[KernelEvents::REQUEST][] = array('onKernelRequestLegacy', 90);
+    $events[KernelEvents::REQUEST][] = array('onKernelRequestLegacy', 31);
 
     return $events;
   }
diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
index fa4be98..35a57d3 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
@@ -15,6 +15,7 @@
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
 use Drupal\Core\ContentNegotiation;
+use Drupal\Core\Page\HtmlPage;
 
 /**
  * Main subscriber for VIEW HTTP responses.
@@ -32,6 +33,23 @@ public function __construct(ContentNegotiation $negotiation) {
   }
 
   /**
+   * 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) {
+      $output = theme('html', array('page_object' => $page));
+
+      $response = new Response((string)$output, $page->getStatusCode());
+
+      $event->setResponse($response);
+    }
+  }
+
+  /**
    * Processes a successful controller into an HTTP 200 response.
    *
    * Some controllers may not return a response object but simply the body of
@@ -62,22 +80,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,
-        );
-      }
-      $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,
@@ -129,29 +139,13 @@ public function onIframeUpload(GetResponseForControllerResultEvent $event) {
   }
 
   /**
-   * Processes a successful controller into an HTTP 200 response.
-   *
-   * Some controllers may not return a response object but simply the body of
-   * one. The VIEW event is called in that case, to allow us to mutate that
-   * body into a Response object. In particular we assume that the return from
-   * an HTML-type response is a render array from a legacy page callback and
-   * render it.
-   *
-   * @param Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event
-   *   The Event to process.
-   */
-  public function onHtml(GetResponseForControllerResultEvent $event) {
-    $page_callback_result = $event->getControllerResult();
-    return new Response(drupal_render_page($page_callback_result));
-  }
-
-  /**
    * 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('onHtmlPage', 10);
     $events[KernelEvents::VIEW][] = array('onView');
 
     return $events;
diff --git a/core/lib/Drupal/Core/Page/HeadElement.php b/core/lib/Drupal/Core/Page/HeadElement.php
new file mode 100644
index 0000000..caf457a
--- /dev/null
+++ b/core/lib/Drupal/Core/Page/HeadElement.php
@@ -0,0 +1,60 @@
+<?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 = '';
+
+  /**
+   * Renders this object to an HTML element string.
+   *
+   * @return string
+   */
+  public function __toString() {
+    $attributes = array_filter($this->attributes);
+
+    // Render the attributes via the attribute template class.
+    // @todo Should HeadElement just extend the Attribute classes?
+    $attributes = new Attribute($attributes);
+    $rendered = (string) $attributes;
+
+    return "<{$this->element}{$rendered} />";
+  }
+
+  /**
+   * Sets an attribute on this element.
+   *
+   * @param type $key
+   *   The attribute to set.
+   * @param type $value
+   *   The value to which to set it.
+   */
+  public function setAttribute($key, $value) {
+    $this->attributes[$key] = $value;
+  }
+}
diff --git a/core/lib/Drupal/Core/Page/HtmlFragment.php b/core/lib/Drupal/Core/Page/HtmlFragment.php
new file mode 100644
index 0000000..abf99a5
--- /dev/null
+++ b/core/lib/Drupal/Core/Page/HtmlFragment.php
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Page\HtmlFragment.
+ */
+
+namespace Drupal\Core\Page;
+
+use Drupal\Core\Asset\AssetBag;
+use Drupal\Core\Asset\AssetBagInterface;
+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 = '';
+
+  public function __construct($content = '') {
+    $this->content = $content;
+    //$this->bag = new AssetBag();
+  }
+
+  /**
+   * Adds another asset bag to those already contained in this fragment.
+   *
+   * @param AssetBagInterface $bag
+   */
+  public function addAssetBag(AssetBagInterface $bag) {
+    $this->bag->addAssetBag($bag);
+  }
+
+  /**
+   * Returns the AssetBag representing the collected assets in this fragment.
+   *
+   * @return AssetBag
+   */
+  public function getAssets() {
+    return $this->bag;
+  }
+
+  /**
+   * 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
+   *
+   * @return \Drupal\Core\Page\HtmlFragment
+   */
+  public function setContent($content) {
+    $this->content = $content;
+    return $this;
+  }
+
+  /**
+   * Gets the main content of this HtmlFragment.
+   *
+   * @return string
+   */
+  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.
+   */
+  public function setTitle($title, $output = Title::CHECK_PLAIN) {
+    $this->title = ($output == PASS_THROUGH) ? $title : check_plain($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
+   */
+  public function getTitle() {
+    return $this->title;
+  }
+
+  /**
+   * Clones the current HtmlFragment instance.
+   */
+  public function __clone() {
+    $this->bag = clone $this->bag;
+  }
+}
diff --git a/core/lib/Drupal/Core/Page/HtmlPage.php b/core/lib/Drupal/Core/Page/HtmlPage.php
new file mode 100644
index 0000000..635ef11
--- /dev/null
+++ b/core/lib/Drupal/Core/Page/HtmlPage.php
@@ -0,0 +1,212 @@
+<?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 {
+
+  /**
+   * An array of Link elements.
+   * @var array
+   */
+  protected $links = array();
+
+  /**
+   * An array of Meta elements.
+   *
+   * @var array
+   */
+  protected $metatags = array();
+
+  /**
+   * 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 $contentTop = '';
+
+  /**
+   * Auxiliary content area, below the main content.
+   *
+   * @var string
+   */
+  protected $contentBottom = '';
+
+  /**
+   * 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();
+  }
+
+  /**
+   * Adds a Link to the page.
+   *
+   * @param \Drupal\Core\Page\Link $link
+   *   A link element to enqueue.
+   * @return \Drupal\Core\Page\HtmlPage
+   *   The invoked object.
+   */
+  public function addLink(Link $link) {
+    $this->links[] = $link;
+    return $this;
+  }
+
+  /**
+   * Returns an array of all enqueued links.
+   *
+   * @return array
+   */
+  public function links() {
+    return $this->links;
+  }
+
+  /**
+   * Adds a Meta tag to the page.
+   *
+   * @param \Drupal\Core\Page\Metatag $tag
+   *   A meta element to add.
+   * @return \Drupal\Core\Page\HtmlPage
+   *   The invoked object.
+   */
+  public function addMetatag(Metatag $tag) {
+    $this->metatags[] = $tag;
+    return $this;
+  }
+
+  /**
+   * Returns an array of all enqueued meta tags.
+   * @return array
+   */
+  public function metatags() {
+    return $this->metatags;
+  }
+
+  /**
+   * 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 getAttributes() {
+    return $this->bodyAttributes;
+  }
+
+  /**
+   * Sets the top-content of this page.
+   *
+   * @param string $content
+   *   The top-content to set.
+   * @return \Drupal\Core\Page\HtmlPage
+   *   The called object.
+   */
+  public function setContentTop($content) {
+    $this->contentTop = $content;
+    return $this;
+  }
+
+  /**
+   * Returns the top-content of this page.
+   *
+   * @return string
+   *   The top-content of this page.
+   */
+  public function getContentTop() {
+    return $this->contentTop;
+  }
+
+  /**
+   * Sets the bottom-content of this page.
+   *
+   * @param string $content
+   *   The bottom-content to set.
+   * @return \Drupal\Core\Page\HtmlPage
+   *   The called object.
+   */
+  public function setContentBottom($content) {
+    $this->contentBottom = $content;
+    return $this;
+  }
+
+  /**
+   * Returns the bottom-content of this page.
+   *
+   * @return string
+   *   The bottom-content of this page.
+   */
+  public function getContentBottom() {
+    return $this->contentBottom;
+  }
+
+  /**
+   * Sets the HTTP status of this page.
+   *
+   * @param int $status
+   *   The status code to set.
+   * @return \Drupal\Core\Page\HtmlPage
+   *   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 thise page.
+   */
+  public function getStatusCode() {
+    return $this->statusCode;
+  }
+}
+
diff --git a/core/lib/Drupal/Core/Page/Link.php b/core/lib/Drupal/Core/Page/Link.php
new file mode 100644
index 0000000..9b4a794
--- /dev/null
+++ b/core/lib/Drupal/Core/Page/Link.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Page\Link.
+ */
+
+namespace Drupal\Core\Page;
+
+/**
+ * Defines a link html HEAD element, which is defined by the href of the link.
+ */
+class Link extends HeadElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $element = 'link';
+
+  /**
+   * Constructs a new Link object.
+   *
+   * @param string $href
+   *   The Link URI. The URI should already be processed to be a fully qualified
+   *   absolute link if necessary.
+   * @param string $rel
+   *   (optional) The link relationship. This is usually an IANA or
+   *   Microformat-defined string. Defaults to an empty string
+   * @param array $attributes
+   *   (Optional) Additional attributes for this element. Defaults to an empty
+   *   array.
+   */
+  public function __construct($href, $rel = '', array $attributes = array()) {
+    $this->attributes = $attributes + array(
+      'href' => $href,
+      'rel' => $rel,
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Page/Metatag.php b/core/lib/Drupal/Core/Page/Metatag.php
new file mode 100644
index 0000000..58e3562
--- /dev/null
+++ b/core/lib/Drupal/Core/Page/Metatag.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Page\Metatag.
+ */
+
+namespace Drupal\Core\Page;
+
+/**
+ * Defines a metatag HTML head element which is defined by the name and content.
+ */
+class Metatag extends HeadElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $element = 'meta';
+
+  /**
+   * Constructs a new Metatag instance.
+   *
+   * @param string $name
+   *   The value of the name attribute.
+   * @param string $content
+   *   (Optional) The value of the content attribute.
+   * @param array $attributes
+   *   (Optional) Additional attributes for this element.
+   */
+  public function __construct($name, $content = '', array $attributes = array()) {
+    $this->attributes = $attributes + array(
+      'name' =>  $name,
+      'content' => $content,
+    );
+  }
+
+}
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..cdb9b4e 100644
--- a/core/lib/Drupal/Core/Routing/Enhancer/FormEnhancer.php
+++ b/core/lib/Drupal/Core/Routing/Enhancer/FormEnhancer.php
@@ -9,7 +9,9 @@
 
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
-use Drupal\Core\ContentNegotiation;
+use Drupal\Core\Controller\HtmlFormController;
+use Drupal\Core\Controller\ControllerResolverInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Enhances a form route with the appropriate controller.
@@ -17,28 +19,34 @@
 class FormEnhancer implements RouteEnhancerInterface {
 
   /**
-   * Content negotiation library.
+   * The service container.
    *
-   * @var \Drupal\CoreContentNegotiation
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
    */
-  protected $negotiation;
+  protected $container;
 
   /**
-   * Constructs a new \Drupal\Core\Routing\Enhancer\FormEnhancer object.
+   * The controller resolver.
    *
-   * @param \Drupal\Core\ContentNegotiation $negotiation
-   *   The Content Negotiation service.
+   * @var \Drupal\Core\Controller\ControllerResolverInterface
+   */
+  protected $resolver;
+
+  /**
+   * Constructs a new \Drupal\Core\Routing\Enhancer\FormEnhancer object.
    */
-  public function __construct(ContentNegotiation $negotiation) {
-    $this->negotiation = $negotiation;
+  public function __construct(ContainerInterface $container, ControllerResolverInterface $resolver) {
+    $this->container = $container;
+    $this->resolver = $resolver;
   }
 
   /**
    * {@inhertdoc}
    */
   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']);
+      $defaults['_content'] = array($wrapper, 'getContentResult');
     }
     return $defaults;
   }
diff --git a/core/modules/aggregator/aggregator.routing.yml b/core/modules/aggregator/aggregator.routing.yml
index ad75ec2..2fd7f50 100644
--- a/core/modules/aggregator/aggregator.routing.yml
+++ b/core/modules/aggregator/aggregator.routing.yml
@@ -50,7 +50,7 @@ aggregator_opml_add:
 aggregator_page_last:
   pattern: '/aggregator'
   defaults:
-    _controller: '\Drupal\aggregator\Controller\AggregatorController::pageLast'
+    _content: '\Drupal\aggregator\Controller\AggregatorController::pageLast'
   requirements:
     _permission: 'access news feeds'
 
diff --git a/core/modules/aggregator/tests/modules/aggregator_test/aggregator_test.module b/core/modules/aggregator/tests/modules/aggregator_test/aggregator_test.module
index 4427a3a..39048df 100644
--- a/core/modules/aggregator/tests/modules/aggregator_test/aggregator_test.module
+++ b/core/modules/aggregator/tests/modules/aggregator_test/aggregator_test.module
@@ -2,6 +2,7 @@
 
 use Drupal\Component\Utility\Crypt;
 use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Implements hook_menu().
@@ -64,7 +65,7 @@ function aggregator_test_feed($use_last_modified = FALSE, $use_etag = FALSE) {
   $feed = fread($handle, filesize($file_name));
   fclose($handle);
 
-  print $feed;
+  return new Response($feed);
 }
 
 /**
diff --git a/core/modules/block/lib/Drupal/block/Routing/RouteSubscriber.php b/core/modules/block/lib/Drupal/block/Routing/RouteSubscriber.php
index 576ea80..4df0e25 100644
--- a/core/modules/block/lib/Drupal/block/Routing/RouteSubscriber.php
+++ b/core/modules/block/lib/Drupal/block/Routing/RouteSubscriber.php
@@ -42,7 +42,7 @@ public function routes(RouteBuildEvent $event) {
       $route = new Route(
         "admin/structure/block/list/$key",
         array(
-          '_controller' => '\Drupal\block\Controller\BlockListController::listing',
+          '_content' => '\Drupal\block\Controller\BlockListController::listing',
           'theme' => $key,
         ),
         array(
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 c26a438..90a9d49 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:
   pattern: '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'
@@ -23,7 +23,7 @@ config_test_entity:
 config_test_entity_edit:
   pattern: 'admin/structure/config_test/manage/{config_test}/edit'
   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/system/lib/Drupal/system/Tests/System/ExceptionControllerTest.php b/core/modules/system/lib/Drupal/system/Tests/System/ExceptionControllerTest.php
index 939432d..8423f10 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/ExceptionControllerTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/ExceptionControllerTest.php
@@ -8,6 +8,8 @@
 
 use \Drupal\Core\ContentNegotiation;
 use \Drupal\Core\Controller\ExceptionController;
+use Drupal\Core\KeyValueStore\MemoryStorage;
+use Drupal\Core\Language\LanguageManager;
 use \Drupal\simpletest\UnitTestBase;
 use \Symfony\Component\HttpFoundation\Request;
 use \Symfony\Component\HttpKernel\Exception\FlattenException;
@@ -31,7 +33,7 @@ public static function getInfo() {
   public function test405HTML() {
     $exception = new \Exception('Test exception');
     $flat_exception = FlattenException::create($exception, 405);
-    $exception_controller = new ExceptionController(new ContentNegotiation());
+    $exception_controller = new ExceptionController(new ContentNegotiation(), new LanguageManager(new MemoryStorage('state')));
     $response = $exception_controller->execute($flat_exception, new Request());
     $this->assertEqual($response->getStatusCode(), 405, 'HTTP status of response is correct.');
     $this->assertEqual($response->getContent(), 'Method Not Allowed', 'HTTP response body is correct.');
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index df7ef6f..e657681 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -5,6 +5,7 @@
  * Admin page callbacks for the system module.
  */
 
+use Drupal\Core\Page\HtmlPage;
 use Drupal\system\DateFormatInterface;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpFoundation\Response;
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index b0e87d8..9f73be9 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -261,7 +261,6 @@ function system_element_info() {
   $types['page'] = array(
     '#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.
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 9b27c08..6d4721d 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -162,7 +162,7 @@ system_theme_enable:
 system_status:
   pattern: '/admin/reports/status'
   defaults:
-    _controller: 'Drupal\system\Controller\SystemInfoController::status'
+    _content: 'Drupal\system\Controller\SystemInfoController::status'
   requirements:
     _permission: 'administer site configuration'
 
diff --git a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php
index 4243f81..045d061 100644
--- a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php
+++ b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php
@@ -55,7 +55,7 @@ public function alterRoutes(RouteBuildEvent $event) {
       $collection = $event->getRouteCollection();
       $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/router_test.routing.yml b/core/modules/system/tests/modules/router_test/router_test.routing.yml
index dc6c5a7..51a816e 100644
--- a/core/modules/system/tests/modules/router_test/router_test.routing.yml
+++ b/core/modules/system/tests/modules/router_test/router_test.routing.yml
@@ -30,7 +30,7 @@ router_test_4:
 router_test_6:
   pattern: '/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/views/lib/Drupal/views/EventSubscriber/HTTPStatusCodeViewSubscriber.php b/core/modules/views/lib/Drupal/views/EventSubscriber/HTTPStatusCodeViewSubscriber.php
new file mode 100644
index 0000000..9adac77
--- /dev/null
+++ b/core/modules/views/lib/Drupal/views/EventSubscriber/HTTPStatusCodeViewSubscriber.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\EventSubscriber\HTTPStatusCodeViewSubscriber.
+ */
+
+namespace Drupal\views\EventSubscriber;
+
+use Drupal\Core\Page\HtmlPage;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Changes the HtmlPage object to have the status code
+ */
+class HTTPStatusCodeViewSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Sets the proper response code coming from the http status area handler.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event
+   *   The Event to process.
+   */
+  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));
+      };
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[KernelEvents::VIEW][] = array('onHtmlPage', 20);
+
+    return $events;
+  }
+
+} 
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 d448876..fbeadbf 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
@@ -65,6 +65,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/display/Page.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/Page.php
index 75758e7..45cfd6d 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
@@ -8,9 +8,13 @@
 namespace Drupal\views\Plugin\views\display;
 
 use Drupal\views\Annotation\ViewsDisplay;
+use Drupal\Component\Utility\Xss;
+use Drupal\views\Views;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Drupal\Core\Annotation\Translation;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
 
 /**
  * The plugin that handles a full page.
@@ -76,6 +80,26 @@ protected function defineOptions() {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function collectRoutes(RouteCollection $collection) {
+    parent::collectRoutes($collection);
+
+    $view_id = $this->view->storage->id();
+    $display_id = $this->display['id'];
+
+    // Move _controller to _content for page displays, which will return a
+    // normal Drupal HTML page.
+    $view_route = $collection->get("view.$view_id.$display_id");
+    $defaults = $view_route->getDefaults();
+    $defaults['_content'] = $defaults['_controller'];
+    unset($defaults['_controller']);
+    $view_route->setDefaults($defaults);
+
+    return $collection;
+  }
+
+  /**
    * Overrides \Drupal\views\Plugin\views\display\PathPluginBase::execute().
    */
   public function execute() {
@@ -91,12 +115,16 @@ public function execute() {
 
     // First execute the view so it's possible to get tokens for the title.
     // And the title, which is much easier.
-    drupal_set_title(filter_xss_admin($this->view->getTitle()), PASS_THROUGH);
-
-    $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)) {
+      return $render + array(
+        '#title' => Xss::filterAdmin($this->view->getTitle()),
+      );
+    }
+    else {
+      return $render;
+    }
   }
 
   /**
diff --git a/core/modules/views/lib/Drupal/views/Routing/ViewPageController.php b/core/modules/views/lib/Drupal/views/Routing/ViewPageController.php
index 8da43f0..b14bd3c 100644
--- a/core/modules/views/lib/Drupal/views/Routing/ViewPageController.php
+++ b/core/modules/views/lib/Drupal/views/Routing/ViewPageController.php
@@ -68,6 +68,7 @@ public function handle(Request $request) {
       throw new NotFoundHttpException(format_string('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/ViewPageControllerTest.php b/core/modules/views/lib/Drupal/views/Tests/ViewPageControllerTest.php
index 892b2be..49e3205 100644
--- a/core/modules/views/lib/Drupal/views/Tests/ViewPageControllerTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/ViewPageControllerTest.php
@@ -25,7 +25,7 @@ class ViewPageControllerTest extends ViewUnitTestBase {
    *
    * @var array
    */
-  public static $modules = array('user');
+  public static $modules = array('user', 'menu_link');
 
   /**
    * Views used by this test.
@@ -56,6 +56,7 @@ protected function setUp() {
     parent::setUp();
 
     $this->installSchema('system', 'menu_router');
+    $this->installSchema('menu_link', 'menu_links');
 
     $this->pageController = new ViewPageController($this->container->get('entity.manager')->getStorageController('view'), new ViewExecutableFactory());
   }
@@ -88,7 +89,8 @@ public function testPageController() {
 
     $request->attributes->set('display_id', 'page_1');
     $output = $this->pageController->handle($request);
-    $this->assertTrue($output instanceof Response, 'Ensure the page display returns a response object.');
+    $this->assertTrue(is_array($output));
+    $this->assertEqual($output['#view']->storage->id, 'test_page_view', 'The right view was executed.');
   }
 
 }
diff --git a/core/modules/views/lib/Drupal/views/ViewExecutable.php b/core/modules/views/lib/Drupal/views/ViewExecutable.php
index 0ce6a2c..9b57d8c 100644
--- a/core/modules/views/lib/Drupal/views/ViewExecutable.php
+++ b/core/modules/views/lib/Drupal/views/ViewExecutable.php
@@ -10,6 +10,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;
 
 /**
@@ -334,6 +335,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.
@@ -1553,7 +1561,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() {
@@ -1564,6 +1572,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/views.services.yml b/core/modules/views/views.services.yml
index c77a27d..7c18064 100644
--- a/core/modules/views/views.services.yml
+++ b/core/modules/views/views.services.yml
@@ -90,3 +90,7 @@ services:
     class: Drupal\views\ViewsAccessCheck
     tags:
       - { name: 'access_check' }
+  views.handler_area_statuscode.view_subscriber:
+    class: Drupal\views\EventSubscriber\HTTPStatusCodeViewSubscriber
+    tags:
+      - { name: 'event_subscriber' }
diff --git a/core/modules/views_ui/views_ui.routing.yml b/core/modules/views_ui/views_ui.routing.yml
index fca076b..d0a04b4 100644
--- a/core/modules/views_ui/views_ui.routing.yml
+++ b/core/modules/views_ui/views_ui.routing.yml
@@ -118,7 +118,7 @@ views_ui.form.addItem:
       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'
@@ -130,7 +130,7 @@ views_ui.form.editDetails:
       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'
@@ -142,7 +142,7 @@ views_ui.form.reorderDisplays:
       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'
@@ -154,7 +154,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'
@@ -166,7 +166,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'
@@ -178,7 +178,7 @@ views_ui.form.rearrangeFilter:
       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'
@@ -190,7 +190,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'
@@ -202,7 +202,7 @@ views_ui.form.configItem:
       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'
@@ -214,7 +214,7 @@ views_ui.form.configItemExtra:
       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'
@@ -226,7 +226,7 @@ views_ui.form.configItemGroup:
       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/tests/Drupal/Tests/Core/Entity/Enhancer/EntityRouteEnhancerTest.php b/core/tests/Drupal/Tests/Core/Entity/Enhancer/EntityRouteEnhancerTest.php
index 17f454e..e880af9 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Enhancer/EntityRouteEnhancerTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Enhancer/EntityRouteEnhancerTest.php
@@ -34,12 +34,13 @@ public static function getInfo() {
    * @see \Drupal\Core\Entity\Enhancer\EntityRouteEnhancer::enhancer()
    */
   public function testEnhancer() {
-    $negotation = $this->getMock('Drupal\core\ContentNegotiation', array('getContentType'));
-    $negotation->expects($this->any())
-      ->method('getContentType')
-      ->will($this->returnValue('html'));
+    $controller_resolver = $this->getMock('Drupal\Core\Controller\ControllerResolverInterface');
+    $entity_manager = $this->getMockBuilder('Drupal\Core\Entity\EntityManager')
+      ->disableOriginalConstructor()
+      ->getMock();
 
-    $route_enhancer = new EntityRouteEnhancer($negotation);
+
+    $route_enhancer = new EntityRouteEnhancer($controller_resolver, $entity_manager);
 
     // Set a controller to ensure it is not overridden.
     $request = new Request();
@@ -47,29 +48,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 +97,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 +107,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']));
