From e2df32fb2e0122b5b828e51e50bcf67476afe565 Mon Sep 17 00:00:00 2001
From: Ian Thomas <ian@ithomas.name>
Date: Wed, 5 Mar 2014 00:23:34 +0000
Subject: [PATCH] 17

---
 core/includes/batch.inc                            |  30 +++---
 core/includes/bootstrap.inc                        |  47 ---------
 core/includes/errors.inc                           |   9 +-
 core/includes/install.core.inc                     | 117 +++++++++++----------
 core/includes/theme.inc                            |  26 +----
 core/includes/update.inc                           |   2 +-
 .../Drupal/Core/Controller/DialogController.php    |   9 +-
 .../Drupal/Core/Controller/HtmlControllerBase.php  |   2 +-
 .../EventSubscriber/MaintenanceModeSubscriber.php  |   2 +-
 .../Exception/AlreadyInstalledException.php        |  39 +++++++
 .../Installer/Exception/InstallerException.php     |  64 +++++++++++
 .../Installer/Exception/NoProfilesException.php    |  31 ++++++
 .../Core/Page/DefaultHtmlFragmentRenderer.php      |   8 --
 core/lib/Drupal/Core/Page/HtmlFragment.php         |   4 +-
 core/lib/Drupal/Core/Utility/Title.php             |   5 +
 .../system/Tests/Common/RenderElementTypesTest.php |  16 +--
 .../Drupal/system/Tests/System/PageTitleTest.php   |  24 +----
 core/modules/system/system.api.php                 |   2 +-
 core/modules/system/system.module                  |   1 +
 .../system/templates/install-page.html.twig        |   4 +-
 .../lib/Drupal/views/Plugin/views/area/Title.php   |   4 +-
 core/themes/seven/install-page.html.twig           |  69 ------------
 core/themes/seven/templates/install-page.html.twig |  69 ++++++++++++
 core/update.php                                    |  40 +++++--
 24 files changed, 340 insertions(+), 284 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Installer/Exception/AlreadyInstalledException.php
 create mode 100644 core/lib/Drupal/Core/Installer/Exception/InstallerException.php
 create mode 100644 core/lib/Drupal/Core/Installer/Exception/NoProfilesException.php
 delete mode 100644 core/themes/seven/install-page.html.twig
 create mode 100644 core/themes/seven/templates/install-page.html.twig

diff --git a/core/includes/batch.inc b/core/includes/batch.inc
index d78248d..0366c10 100644
--- a/core/includes/batch.inc
+++ b/core/includes/batch.inc
@@ -47,42 +47,38 @@ function _batch_page(Request $request) {
   // Register database update for the end of processing.
   drupal_register_shutdown_function('_batch_shutdown');
 
+  $build = array();
+
   // Add batch-specific CSS.
-  $attached = array('#attached' => array('css' => array()));
   foreach ($batch['sets'] as $batch_set) {
     if (isset($batch_set['css'])) {
       foreach ($batch_set['css'] as $css) {
-        $attached['#attached']['css'][$css] = array();
+        $build['#attached']['css'][$css] = array();
       }
     }
   }
-  drupal_render($attached);
 
   $op = $request->get('op', '');
-  $output = NULL;
   switch ($op) {
     case 'start':
+    case 'do_nojs':
       // Display the full progress page on startup and on each additional
       // non-JavaScript iteration.
-      $output = _batch_progress_page();
+      $current_set = _batch_current_set();
+      $build['#title'] = $current_set['title'];
+      $build['content'] = _batch_progress_page();
       break;
 
     case 'do':
       // JavaScript-based progress page callback.
-      $output = _batch_do();
-      break;
-
-    case 'do_nojs':
-      // Non-JavaScript-based progress page.
-      $output = _batch_progress_page();
-      break;
+      return _batch_do();
 
     case 'finished':
-      $output = _batch_finished();
-      break;
+      // _batch_finished() returns a RedirectResponse.
+      return _batch_finished();
   }
 
-  return $output;
+  return $build;
 }
 
 /**
@@ -107,7 +103,6 @@ function _batch_progress_page() {
   $batch = &batch_get();
 
   $current_set = _batch_current_set();
-  drupal_set_title($current_set['title'], PASS_THROUGH);
 
   $new_op = 'do_nojs';
 
@@ -128,6 +123,7 @@ function _batch_progress_page() {
     $fallback = $current_set['error_message'] . '<br />' . $batch['error_message'];
     $fallback = array(
       '#theme' => 'maintenance_page',
+      '#title' => $current_set['title'],
       '#content' => $fallback,
       '#show_messages' => FALSE,
     );
@@ -195,7 +191,7 @@ function _batch_progress_page() {
       ),
     ),
   );
-  return drupal_render($build);
+  return $build;
 }
 
 /**
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index cb75933..ba35074 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -184,13 +184,6 @@
 define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
 
 /**
- * Flag for drupal_set_title(); text has already been sanitized.
- *
- * @todo Move to the Title class.
- */
-const PASS_THROUGH = -1;
-
-/**
  * Regular expression to match PHP function names.
  *
  * @see http://php.net/manual/language.functions.php
@@ -1400,46 +1393,6 @@ function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
 }
 
 /**
- * Gets the title of the current page.
- *
- * The title is displayed on the page and in the title bar.
- *
- * @return
- *   The current page's title.
- */
-function drupal_get_title() {
-  return drupal_set_title() ?: '';
-}
-
-/**
- * Sets the title of the current page.
- *
- * The title is displayed on the page and in the title bar.
- *
- * @param $title
- *   Optional string value to assign to the page title; or if set to NULL
- *   (default), leaves the current title unchanged.
- * @param $output
- *   Optional flag - normally should be left as Title::CHECK_PLAIN. Only set to
- *   PASS_THROUGH if you have already removed any possibly dangerous code
- *   from $title using a function like
- *   \Drupal\Component\Utility\String::checkPlain() or filter_xss(). With this
- *   flag the string will be passed through unchanged.
- *
- * @return
- *   The updated title of the current page.
- */
-function drupal_set_title($title = NULL, $output = Title::CHECK_PLAIN) {
-  $stored_title = &drupal_static(__FUNCTION__);
-
-  if (isset($title)) {
-    $stored_title = ($output == PASS_THROUGH) ? $title : String::checkPlain($title);
-  }
-
-  return $stored_title;
-}
-
-/**
  * Generates a default anonymous $user object.
  *
  * @return \Drupal\Core\Session\AccountInterface
diff --git a/core/includes/errors.inc b/core/includes/errors.inc
index 761b7bd..56c276f 100644
--- a/core/includes/errors.inc
+++ b/core/includes/errors.inc
@@ -218,19 +218,22 @@ function _drupal_log_error($error, $fatal = FALSE) {
     }
 
     if ($fatal) {
-      // Should not translate the string to avoid errors producing more errors.
-      drupal_set_title('Error');
       // We fallback to a maintenance page at this point, because the page generation
       // itself can generate errors.
       // Should not translate the string to avoid errors producing more errors.
       $message = 'The website has encountered an error. Please try again later.';
       if ($is_installer) {
         // install_display_output() prints the output and ends script execution.
-        install_display_output($message, $GLOBALS['install_state']);
+        $output = array(
+          '#title' => 'Error',
+          '#markup' => $message,
+        );
+        install_display_output($output, $GLOBALS['install_state']);
       }
       else {
         $output = array(
           '#theme' => 'maintenance_page',
+          '#title' => 'Error',
           '#content' => $message,
         );
         $output = drupal_render($output);
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 8d08d94..0118865 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -10,6 +10,9 @@
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\DatabaseExceptionWrapper;
 use Drupal\Core\Database\Install\TaskException;
+use Drupal\Core\Installer\Exception\AlreadyInstalledException;
+use Drupal\Core\Installer\Exception\InstallerException;
+use Drupal\Core\Installer\Exception\NoProfilesException;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageManager;
 use Drupal\Core\StringTranslation\Translator\FileTranslation;
@@ -89,12 +92,24 @@ function install_drupal($settings = array()) {
   $interactive = empty($settings);
   $install_state = $settings + array('interactive' => $interactive) + install_state_defaults();
 
-  // Begin the page request. This adds information about the current state of
-  // the Drupal installation to the passed-in array.
-  install_begin_request($install_state);
-  // Based on the installation state, run the remaining tasks for this page
-  // request, and collect any output.
-  $output = install_run_tasks($install_state);
+  try {
+    // Begin the page request. This adds information about the current state of
+    // the Drupal installation to the passed-in array.
+    install_begin_request($install_state);
+    // Based on the installation state, run the remaining tasks for this page
+    // request, and collect any output.
+    $output = install_run_tasks($install_state);
+  }
+  catch (InstallerException $e) {
+    // In the non-interactive installer, exceptions are always thrown directly.
+    if (!$install_state['interactive']) {
+      throw $e;
+    }
+    $output = array(
+      '#title' => $e->getTitle(),
+      '#markup' => $e->getMessage(),
+    );
+  }
 
   // After execution, all tasks might be complete, in which case
   // $install_state['installation_finished'] is TRUE. In case the last task
@@ -526,7 +541,7 @@ function install_begin_request(&$install_state) {
 
     // Do not install over a configured settings.php.
     if (!empty($GLOBALS['databases'])) {
-      throw new Exception(install_already_done_error());
+      throw new AlreadyInstalledException($container->get('string_translation'));
     }
   }
 
@@ -536,7 +551,7 @@ function install_begin_request(&$install_state) {
     $config = glob(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY) . '/*.' . FileStorage::getFileExtension());
     if (!empty($config)) {
       $task = NULL;
-      throw new Exception(install_already_done_error());
+      throw new AlreadyInstalledException($container->get('string_translation'));
     }
   }
 
@@ -627,7 +642,7 @@ function install_run_task($task, &$install_state) {
       // rendered, which means the task is not complete yet.
       if (empty($form_state['executed'])) {
         $install_state['task_not_complete'] = TRUE;
-        return drupal_render($form);
+        return $form;
       }
       // Otherwise, return nothing so the next task will run in the same
       // request.
@@ -647,7 +662,7 @@ function install_run_task($task, &$install_state) {
       drupal_form_submit($function, $form_state);
       $errors = form_get_errors($form_state);
       if (!empty($errors)) {
-        throw new Exception(implode("\n", $errors));
+        throw new InstallerException(implode("\n", $errors));
       }
     }
   }
@@ -954,7 +969,7 @@ function install_full_redirect_url($install_state) {
 /**
  * Displays themed installer output and ends the page request.
  *
- * Installation tasks should use drupal_set_title() to set the desired page
+ * Installation tasks should use #title to set the desired page
  * title, but otherwise this function takes care of theming the overall page
  * output during every step of the installation.
  *
@@ -1000,7 +1015,17 @@ function install_display_output($output, $install_state) {
     );
     drupal_add_region_content('sidebar_first', drupal_render($task_list));
   }
-  $install_page = array('#theme' => 'install_page', '#content' => $output);
+  $install_page = array(
+    '#theme' => 'install_page',
+    // $output has to be rendered here, because the install page template is not
+    // wrapped into the html template, which means that any #attached libraries
+    // in $output will not be loaded, because the wrapping HTML has been printed
+    // already.
+    '#content' => drupal_render($output),
+  );
+  if (isset($output['#title'])) {
+    $install_page['#page']['#title'] = $output['#title'];
+  }
   print drupal_render($install_page);
   exit;
 }
@@ -1072,11 +1097,11 @@ function install_verify_completed_task() {
   }
   // Do not trigger an error if the database query fails, since the database
   // might not be set up yet.
-  catch (Exception $e) {
+  catch (\Exception $e) {
   }
   if (isset($task)) {
     if ($task == 'done') {
-      throw new Exception(install_already_done_error());
+      throw new AlreadyInstalledException(\Drupal::service('string_translation'));
     }
     return $task;
   }
@@ -1114,7 +1139,7 @@ function install_settings_form($form, &$form_state, &$install_state) {
   $conf_path = './' . conf_path(FALSE);
   $settings_file = $conf_path . '/settings.php';
 
-  drupal_set_title(t('Database configuration'));
+  $form['#title'] = t('Database configuration');
 
   $drivers = drupal_get_database_types();
   $drivers_keys = array_keys($drivers);
@@ -1314,13 +1339,10 @@ function install_select_profile(&$install_state) {
       // yet), rather just a convenience method for setting parameters in the
       // URL.
       if ($install_state['interactive']) {
-        include_once __DIR__ . '/form.inc';
-        drupal_set_title(t('Select an installation profile'));
-        $form = drupal_get_form('install_select_profile_form', $install_state);
-        return drupal_render($form);
+        return drupal_get_form('install_select_profile_form', $install_state);
       }
       else {
-        throw new Exception(install_no_profile_error());
+        throw new NoProfilesException(\Drupal::service('string_translation'));
       }
     }
     else {
@@ -1384,9 +1406,10 @@ function _install_select_profile($profiles) {
  * @ingroup forms
  */
 function install_select_profile_form($form, &$form_state, $install_state) {
+  $form['#title'] = t('Select an installation profile');
+
   $profiles = array();
   $names = array();
-
   foreach ($install_state['profiles'] as $profile) {
     $details = install_profile_info($profile->name);
     // Skip this extension if its type is not profile.
@@ -1560,10 +1583,7 @@ function install_select_language(&$install_state) {
     // translation files were found the form shows a select list of the
     // corresponding languages to choose from.
     if ($install_state['interactive']) {
-      drupal_set_title(t('Choose language'));
-      include_once __DIR__ . '/form.inc';
-      $elements = drupal_get_form('install_select_language_form', count($files) > 1 ? $files : array());
-      return drupal_render($elements);
+      return drupal_get_form('install_select_language_form', count($files) > 1 ? $files : array());
     }
     // If we are performing a non-interactive installation. If only one language
     // (English) is available, assume the user knows what he is doing. Otherwise
@@ -1574,7 +1594,7 @@ function install_select_language(&$install_state) {
         return;
       }
       else {
-        throw new Exception(t('Sorry, you must select a language to continue the installation.'));
+        throw new InstallerException(t('Sorry, you must select a language to continue the installation.'));
       }
     }
   }
@@ -1595,6 +1615,8 @@ function install_select_language_form($form, &$form_state, $files = array()) {
   $select_options = array();
   $browser_options = array();
 
+  $form['#title'] = t('Choose language');
+
   // Build a select list with language names in native language for the user
   // to choose from. And build a list of available languages for the browser
   // to select the language default from.
@@ -1873,24 +1895,6 @@ function _install_get_version_info($version) {
 }
 
 /**
- * Indicates that there are no profiles available.
- */
-function install_no_profile_error() {
-  drupal_set_title(t('No profiles available'));
-  return t('We were unable to find any installation profiles. Installation profiles tell us what modules to enable and what schema to install in the database. A profile is necessary to continue with the installation process.');
-}
-
-/**
- * Indicates that Drupal has already been installed.
- */
-function install_already_done_error() {
-  global $base_url;
-
-  drupal_set_title(t('Drupal already installed'));
-  return t('<ul><li>To start over, you must empty your existing database, delete your active configuration, and copy <em>default.settings.php</em> over <em>settings.php</em>.</li><li>To install to a different database, edit the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</li><li>To locate your active configuration, view the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</li><li>To upgrade an existing installation, proceed to the <a href="@base-url/core/update.php">update script</a>.</li><li>View your <a href="@base-url">existing site</a>.</li></ul>', array('@base-url' => $base_url));
-}
-
-/**
  * Loads information about the chosen profile during installation.
  *
  * @param $install_state
@@ -1906,7 +1910,7 @@ function install_load_profile(&$install_state) {
     $install_state['profile_info'] = install_profile_info($install_state['parameters']['profile'], $install_state['parameters']['langcode']);
   }
   else {
-    throw new Exception(t('Sorry, the profile you have chosen cannot be loaded.'));
+    throw new InstallerException(t('Sorry, the profile you have chosen cannot be loaded.'));
   }
 }
 
@@ -2072,7 +2076,7 @@ function _install_prepare_import($langcode) {
  * @ingroup forms
  */
 function install_configure_form($form, &$form_state, &$install_state) {
-  drupal_set_title(t('Configure site'));
+  $form['#title'] = t('Configure site');
 
   // Warn about settings.php permissions risk
   $settings_dir = conf_path();
@@ -2174,8 +2178,6 @@ function install_finished(&$install_state) {
   // registered by the installation profile are registered correctly.
   drupal_flush_all_caches();
 
-  drupal_set_title(t('@drupal installation complete', array('@drupal' => drupal_install_profile_distribution_name())), PASS_THROUGH);
-
   $messages = drupal_set_message();
   $output = '<p>' . t('Congratulations, you installed @drupal!', array('@drupal' => drupal_install_profile_distribution_name())) . '</p>';
   // Ensure the URL that is generated for the home page does not have 'install.php'
@@ -2197,7 +2199,11 @@ function install_finished(&$install_state) {
   $snapshot = \Drupal::service('config.storage.snapshot');
   \Drupal::service('config.manager')->createSnapshot($active, $snapshot);
 
-  return $output;
+  $build = array(
+    '#title' => t('@drupal installation complete', array('@drupal' => drupal_install_profile_distribution_name())),
+    '#markup' => $output,
+  );
+  return $build;
 }
 
 /**
@@ -2525,7 +2531,7 @@ function install_check_requirements($install_state) {
  *   in the URL. Otherwise, no output is returned, so that the next task can be
  *   run in the same page request.
  *
- * @thows \Exception
+ * @throws \Drupal\Core\Installer\Exception\InstallerException
  */
 function install_display_requirements($install_state, $requirements) {
   // Check the severity of the requirements reported.
@@ -2536,14 +2542,13 @@ function install_display_requirements($install_state, $requirements) {
   // and indicating a desire to continue anyway. See drupal_requirements_url().
   if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && empty($install_state['parameters']['continue']))) {
     if ($install_state['interactive']) {
-      drupal_set_title(t('Requirements problem'));
-      $status_report = array(
+      $build['#title'] = t('Requirements problem');
+      $build['report'] = array(
         '#theme' => 'status_report',
         '#requirements' => $requirements,
+        '#suffix' => t('Check the messages and <a href="!url">try again</a>.', array('!url' => check_url(drupal_requirements_url($severity)))),
       );
-      $status_report = drupal_render($status_report);
-      $status_report .= t('Check the messages and <a href="!url">try again</a>.', array('!url' => check_url(drupal_requirements_url($severity))));
-      return $status_report;
+      return $build;
     }
     else {
       // Throw an exception showing any unmet requirements.
@@ -2557,7 +2562,7 @@ function install_display_requirements($install_state, $requirements) {
         }
       }
       if (!empty($failures)) {
-        throw new \Exception(implode("\n\n", $failures));
+        throw new InstallerException(implode("\n\n", $failures));
       }
     }
   }
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 782c243..7ea809a 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -2040,12 +2040,6 @@ function template_preprocess_html(&$variables) {
       'name' => String::checkPlain($site_config->get('name')),
     );
   }
-  elseif (drupal_get_title()) {
-    $head_title = array(
-      'title' => strip_tags(drupal_get_title()),
-      'name' => String::checkPlain($site_config->get('name')),
-    );
-  }
   // @todo Remove once views is not bypassing the view subscriber anymore.
   //   @see http://drupal.org/node/2068471
   elseif (drupal_is_front_page()) {
@@ -2131,6 +2125,7 @@ function template_preprocess_page(&$variables) {
 
   // Move some variables to the top level for themer convenience and template cleanliness.
   $variables['show_messages'] = $variables['page']['#show_messages'];
+  $variables['title'] = $variables['page']['#title'];
 
   foreach (system_region_list($GLOBALS['theme']) as $region_key => $region_name) {
     if (!isset($variables['page'][$region_key])) {
@@ -2160,13 +2155,6 @@ function template_preprocess_page(&$variables) {
   $variables['site_slogan']       = (theme_get_setting('features.slogan') ? filter_xss_admin($site_config->get('slogan')) : '');
   $variables['tabs']              = menu_local_tabs();
 
-  if (isset($variables['page']['#title'])) {
-    $variables['title'] = $variables['page']['#title'];
-  }
-  else {
-    $variables['title'] = new RenderWrapper('drupal_get_title');
-  }
-
   // Pass the main menu and secondary menu to the template as render arrays.
   if (!empty($variables['main_menu'])) {
     $variables['main_menu'] = array(
@@ -2351,12 +2339,6 @@ function template_preprocess_maintenance_page(&$variables) {
       'name' => String::checkPlain($site_config->get('name')),
     );
   }
-  elseif (drupal_get_title()) {
-    $head_title = array(
-      'title' => strip_tags(drupal_get_title()),
-      'name' => String::checkPlain($site_config->get('name')),
-    );
-  }
   else {
     $head_title = array('name' => String::checkPlain($site_name));
     if ($site_slogan) {
@@ -2424,9 +2406,6 @@ function template_preprocess_maintenance_page(&$variables) {
   if (isset($variables['page']['#title'])) {
     $variables['title'] = $variables['page']['#title'];
   }
-  if (!isset($variables['title'])) {
-    $variables['title'] = drupal_get_title();
-  }
 }
 
 /**
@@ -2447,6 +2426,7 @@ function template_preprocess_maintenance_page(&$variables) {
  */
 function template_preprocess_install_page(&$variables) {
   template_preprocess_maintenance_page($variables);
+  $variables['attributes']['class'][] = 'install-page';
   // Override the site name that is displayed on the page, since Drupal is
   // still in the process of being installed.
   $variables['site_name'] = drupal_install_profile_distribution_name();
@@ -2561,7 +2541,7 @@ function drupal_common_theme() {
       'template' => 'maintenance-page',
     ),
     'install_page' => array(
-      'variables' => array('content' => NULL, 'show_messages' => TRUE),
+      'variables' => array('content' => NULL, 'show_messages' => TRUE, 'page' => array()),
       'template' => 'install-page',
     ),
     'task_list' => array(
diff --git a/core/includes/update.inc b/core/includes/update.inc
index fc1e0af..0bca244 100644
--- a/core/includes/update.inc
+++ b/core/includes/update.inc
@@ -147,7 +147,6 @@ function update_check_requirements($skip_warnings = FALSE) {
   // them if the caller has indicated they should be skipped.
   if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && !$skip_warnings)) {
     update_task_list('requirements');
-    drupal_set_title('Requirements problem');
     $status = array(
       '#theme' => 'status_report',
       '#requirements' => $requirements,
@@ -157,6 +156,7 @@ function update_check_requirements($skip_warnings = FALSE) {
     drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
     $maintenance_page = array(
       '#theme' => 'maintenance_page',
+      '#title' => 'Requirements problem',
       '#content' => $status_report,
     );
     print drupal_render($maintenance_page);
diff --git a/core/lib/Drupal/Core/Controller/DialogController.php b/core/lib/Drupal/Core/Controller/DialogController.php
index 5fda730..e86b9bd 100644
--- a/core/lib/Drupal/Core/Controller/DialogController.php
+++ b/core/lib/Drupal/Core/Controller/DialogController.php
@@ -93,14 +93,7 @@ public function dialog(Request $request, $_content, $modal = FALSE) {
     }
 
     $content = drupal_render($page_content);
-
-    // @todo Remove use of drupal_get_title() when
-    //  http://drupal.org/node/1871596 is in.
-    if (!$title = $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT))) {
-      // @todo Remove use of drupal_get_title() when
-      //  http://drupal.org/node/1871596 is in.
-      $title = drupal_get_title();
-    }
+    $title = $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT));
     $response = new AjaxResponse();
     // Fetch any modal options passed in from data-dialog-options.
     if (!($options = $request->request->get('dialogOptions'))) {
diff --git a/core/lib/Drupal/Core/Controller/HtmlControllerBase.php b/core/lib/Drupal/Core/Controller/HtmlControllerBase.php
index 0204bc0..9cc7651 100644
--- a/core/lib/Drupal/Core/Controller/HtmlControllerBase.php
+++ b/core/lib/Drupal/Core/Controller/HtmlControllerBase.php
@@ -80,7 +80,7 @@ protected function createHtmlFragment($page_content, Request $request) {
       $fragment->setTitle($page_content['#title'], Title::FILTER_XSS_ADMIN);
     }
     else if ($route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)) {
-      $fragment->setTitle($this->titleResolver->getTitle($request, $route), PASS_THROUGH);
+      $fragment->setTitle($this->titleResolver->getTitle($request, $route), Title::PASS_THROUGH);
     }
 
     return $fragment;
diff --git a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
index 39aad11..93bfb42 100644
--- a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
@@ -44,9 +44,9 @@ public function onKernelRequestMaintenance(GetResponseEvent $event) {
     if ($request->attributes->get('_maintenance') != MENU_SITE_ONLINE && !($response instanceof RedirectResponse)) {
       // Deliver the 503 page.
       drupal_maintenance_theme();
-      drupal_set_title(t('Site under maintenance'));
       $maintenance_page = array(
         '#theme' => 'maintenance_page',
+        '#title' => t('Site under maintenance'),
         '#content' => filter_xss_admin(
           t(\Drupal::config('system.maintenance')->get('message'), array('@site' => \Drupal::config('system.site')->get('name')))
         ),
diff --git a/core/lib/Drupal/Core/Installer/Exception/AlreadyInstalledException.php b/core/lib/Drupal/Core/Installer/Exception/AlreadyInstalledException.php
new file mode 100644
index 0000000..3f16728
--- /dev/null
+++ b/core/lib/Drupal/Core/Installer/Exception/AlreadyInstalledException.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Installer\Exception\AlreadyInstalledException.
+ */
+
+namespace Drupal\Core\Installer\Exception;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+/**
+ * Exception thrown if Drupal is installed already.
+ */
+class AlreadyInstalledException extends InstallerException {
+
+  /**
+   * Constructs a new "already installed" exception.
+   *
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translation manager.
+   */
+  public function __construct(TranslationInterface $string_translation) {
+    $this->stringTranslation = $string_translation;
+
+    $title = $this->t('Drupal already installed');
+    $message = $this->t('<ul>
+<li>To start over, you must empty your existing database, delete your active configuration, and copy <em>default.settings.php</em> over <em>settings.php</em>.</li>
+<li>To install to a different database, edit the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</li>
+<li>To locate your active configuration, view the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</li>
+<li>To upgrade an existing installation, proceed to the <a href="@base-url/core/update.php">update script</a>.</li>
+<li>View your <a href="@base-url">existing site</a>.</li>
+</ul>', array(
+      '@base-url' => $GLOBALS['base_url'],
+    ));
+    parent::__construct($message, $title);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Installer/Exception/InstallerException.php b/core/lib/Drupal/Core/Installer/Exception/InstallerException.php
new file mode 100644
index 0000000..e8c1ec3
--- /dev/null
+++ b/core/lib/Drupal/Core/Installer/Exception/InstallerException.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Installer\Exception\InstallerException.
+ */
+
+namespace Drupal\Core\Installer\Exception;
+
+/**
+ * Base class for exceptions thrown by installer.
+ */
+class InstallerException extends \RuntimeException {
+
+  /**
+   * The page title to output.
+   *
+   * @var string
+   */
+  protected $title;
+
+  /**
+   * The string translation manager.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface
+   */
+  protected $stringTranslation;
+
+  /**
+   * Constructs a new installer exception.
+   *
+   * @param string $title
+   *   The page title.
+   * @param string $message
+   *   (optional) The exception message. Defaults to 'Error'.
+   * @param int $code
+   *   (optional) The exception code. Defaults to 0.
+   * @param \Exception $previous
+   *   (optional) A previous exception.
+   */
+  public function __construct($message, $title = 'Error', $code = 0, \Exception $previous = NULL) {
+    parent::__construct($message, $code, $previous);
+    $this->title = $title;
+  }
+
+  /**
+   * Returns the exception page title.
+   *
+   * @return string
+   */
+  public function getTitle() {
+    return $this->title;
+  }
+
+  /**
+   * Translates a string using StringTranslation.
+   *
+   * @see \Drupal\Core\StringTranslation\TranslationInterface::translate()
+   */
+  protected function t($string, array $args = array(), array $options = array()) {
+    return $this->stringTranslation->translate($string, $args, $options);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Installer/Exception/NoProfilesException.php b/core/lib/Drupal/Core/Installer/Exception/NoProfilesException.php
new file mode 100644
index 0000000..cc8d596
--- /dev/null
+++ b/core/lib/Drupal/Core/Installer/Exception/NoProfilesException.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Installer\Exception\NoProfilesException.
+ */
+
+namespace Drupal\Core\Installer\Exception;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+/**
+ * Exception thrown if no installation profiles are available.
+ */
+class NoProfilesException extends InstallerException {
+
+  /**
+   * Constructs a new "no profiles available" exception.
+   *
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translation manager.
+   */
+  public function __construct(TranslationInterface $string_translation) {
+    $this->stringTranslation = $string_translation;
+
+    $title = $this->t('No profiles available');
+    $message = $this->t('We were unable to find any installation profiles. Installation profiles tell us what modules to enable and what schema to install in the database. A profile is necessary to continue with the installation process.');
+    parent::__construct($message, $title);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php b/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php
index 64aed01..6e0c509 100644
--- a/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php
+++ b/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php
@@ -74,14 +74,6 @@ public function render(HtmlFragment $fragment, $status_code = 200) {
    *   The modified page object.
    */
   public function preparePage(HtmlPage $page, &$page_array) {
-    // @todo Remove this one drupal_get_title() has been eliminated.
-    if (!$page->hasTitle()) {
-      $title = drupal_get_title();
-      // drupal_set_title() already ensured security, so not letting the
-      // title pass through would cause double escaping.
-      $page->setTitle($title, PASS_THROUGH);
-    }
-
     $page_array['#page'] = $page;
 
     // HTML element attributes.
diff --git a/core/lib/Drupal/Core/Page/HtmlFragment.php b/core/lib/Drupal/Core/Page/HtmlFragment.php
index cc5cac5..cf1ca4d 100644
--- a/core/lib/Drupal/Core/Page/HtmlFragment.php
+++ b/core/lib/Drupal/Core/Page/HtmlFragment.php
@@ -103,8 +103,8 @@ public function getContent() {
    *   Value to assign to the page title.
    * @param int $output
    *   (optional) normally should be left as Title::CHECK_PLAIN. Only set to
-   *   PASS_THROUGH if you have already removed any possibly dangerous code
-   *   from $title using a function like
+   *   Title::PASS_THROUGH if you have already removed any possibly dangerous
+   *   code from $title using a function like
    *   \Drupal\Component\Utility\String::checkPlain() or
    *   \Drupal\Component\Utility\Xss::filterAdmin(). With this flag the string
    *   will be passed through unchanged.
diff --git a/core/lib/Drupal/Core/Utility/Title.php b/core/lib/Drupal/Core/Utility/Title.php
index b6380b6..40a3a91 100644
--- a/core/lib/Drupal/Core/Utility/Title.php
+++ b/core/lib/Drupal/Core/Utility/Title.php
@@ -22,4 +22,9 @@ class Title {
    */
   const FILTER_XSS_ADMIN = 1;
 
+  /**
+   * For controller titles, text has already been sanitized.
+   */
+  const PASS_THROUGH = -1;
+
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/RenderElementTypesTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/RenderElementTypesTest.php
index 56b8aee..d19d17f 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/RenderElementTypesTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/RenderElementTypesTest.php
@@ -163,17 +163,9 @@ function testMaintenancePage() {
     $site_config = \Drupal::config('system.site');
     $site_name = $site_config->get('name');
     $site_slogan = $site_config->get('slogan');
-    if ($title = drupal_get_title()) {
-      $head_title = array(
-        'title' => strip_tags($title),
-        'name' => String::checkPlain($site_config->get('name')),
-      );
-    }
-    else {
-      $head_title = array('name' => String::checkPlain($site_name));
-      if ($site_slogan) {
-        $head_title['slogan'] = strip_tags(Xss::filterAdmin($site_slogan));
-      }
+    $head_title = array('name' => String::checkPlain($site_name));
+    if ($site_slogan) {
+      $head_title['slogan'] = strip_tags(Xss::filterAdmin($site_slogan));
     }
     $head_title = implode(' | ', $head_title);
 
@@ -237,7 +229,7 @@ function testMaintenancePage() {
       '!front_page' => url(),
       '!logo' => theme_get_setting('logo.url'),
       '!site_name' => $site_config->get('name'),
-      '!title' => $title ? '<h1>' . $title . '</h1>' : '',
+      '!title' => $site_name ? '<h1>' . String::checkPlain($site_name) . '</h1>' : '',
       '!content' => '<span>foo</span>',
     );
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/System/PageTitleTest.php b/core/modules/system/lib/Drupal/system/Tests/System/PageTitleTest.php
index 33f2f47..011e550 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/PageTitleTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/PageTitleTest.php
@@ -28,7 +28,7 @@ class PageTitleTest extends WebTestBase {
   public static function getInfo() {
     return array(
       'name' => 'Page titles',
-      'description' => 'Tests correct handling or conversion by drupal_set_title() and drupal_get_title() and checks the correct escaping of site name and slogan.',
+      'description' => 'Tests correct escaping of page title, site name and slogan.',
       'group' => 'System'
     );
   }
@@ -43,32 +43,13 @@ function setUp() {
 
     $this->content_user = $this->drupalCreateUser(array('create page content', 'access content', 'administer themes', 'administer site configuration'));
     $this->drupalLogin($this->content_user);
-    $this->saved_title = drupal_get_title();
   }
 
   /**
-   * Reset page title.
-   */
-  function tearDown() {
-    // Restore the page title.
-    drupal_set_title($this->saved_title, PASS_THROUGH);
-
-    parent::tearDown();
-  }
-
-  /**
-   * Tests the handling of HTML by drupal_set_title() and drupal_get_title()
+   * Tests the handling of HTML in node titles
    */
   function testTitleTags() {
     $title = "string with <em>HTML</em>";
-    // drupal_set_title's $filter is Title::CHECK_PLAIN by default, so the title should be
-    // returned with check_plain().
-    drupal_set_title($title, Title::CHECK_PLAIN);
-    $this->assertTrue(strpos(drupal_get_title(), '<em>') === FALSE, 'Tags in title converted to entities when $output is Title::CHECK_PLAIN.');
-    // drupal_set_title's $filter is passed as PASS_THROUGH, so the title should be
-    // returned with HTML.
-    drupal_set_title($title, PASS_THROUGH);
-    $this->assertTrue(strpos(drupal_get_title(), '<em>') !== FALSE, 'Tags in title are not converted to entities when $output is PASS_THROUGH.');
     // Generate node content.
     $edit = array(
       'title[0][value]' => '!SimpleTest! ' . $title . $this->randomName(20),
@@ -82,6 +63,7 @@ function testTitleTags() {
     $this->drupalGet("node/" . $node->id());
     $this->assertText(check_plain($edit['title[0][value]']), 'Check to make sure tags in the node title are converted.');
   }
+
   /**
    * Test if the title of the site is XSS proof.
    */
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index d36f5f3..5762c5f 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -2250,7 +2250,7 @@ function hook_uninstall() {
  * installer to pause and display a page to the user by returning any themed
  * output that should be displayed on that page (but see below for tasks that
  * use the form API or batch API; the return values of these task functions are
- * handled differently). You should also use drupal_set_title() within the task
+ * handled differently). You should also use #title within the task
  * callback function to set a custom page title. For some tasks, however, you
  * may want to simply do some processing and pass control to the next task
  * without ending the page request; to indicate this, simply do not send back
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 67b25fe..cd77d88 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -282,6 +282,7 @@ function system_element_info() {
   $types['page'] = array(
     '#show_messages' => TRUE,
     '#theme' => 'page',
+    '#title' => '',
   );
   // 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/templates/install-page.html.twig b/core/modules/system/templates/install-page.html.twig
index a462a5a..031013e 100644
--- a/core/modules/system/templates/install-page.html.twig
+++ b/core/modules/system/templates/install-page.html.twig
@@ -12,14 +12,14 @@
  */
 #}
 <!DOCTYPE html>
-<html lang="{{ language.langcode }}" dir="{{ language.dir }}">
+<html{{ html_attributes }}>
 <head>
   {{ head }}
   <title>{{ head_title }}</title>
   {{ styles }}
   {{ scripts }}
 </head>
-<body class="install-page">
+<body class="{{ attributes.class }}">
 
   <div class="l-container">
 
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/area/Title.php b/core/modules/views/lib/Drupal/views/Plugin/views/area/Title.php
index 66f3495..bbc26ec 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/area/Title.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/area/Title.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\views\Plugin\views\area;
 
+use \Drupal\Core\Utility\Title as UtilityTitle;
+
 /**
  * Views area title override handler.
  *
@@ -51,7 +53,7 @@ public function preRender(array $results) {
     // If a title is provided, process it.
     if (!empty($this->options['title'])) {
       $value = $this->globalTokenReplace($this->options['title']);
-      $this->view->setTitle($this->sanitizeValue($value, 'xss_admin'), PASS_THROUGH);
+      $this->view->setTitle($this->sanitizeValue($value, 'xss_admin'), UtilityTitle::PASS_THROUGH);
     }
   }
 
diff --git a/core/themes/seven/install-page.html.twig b/core/themes/seven/install-page.html.twig
deleted file mode 100644
index 1e9a1c2..0000000
--- a/core/themes/seven/install-page.html.twig
+++ /dev/null
@@ -1,69 +0,0 @@
-{#
-/**
- * @file
- * Seven theme implementation to display a Drupal installation page.
- *
- * All the available variables are mirrored in html.html.twig and
- * page.html.twig.
- * Some may be blank but they are provided for consistency.
- *
- * @see template_preprocess_install_page()
- *
- * @ingroup themeable
- */
-#}
-<!DOCTYPE html>
-<html lang="{{ language.langcode }}" dir="{{ language.dir }}" class="install-background">
-<head>
-  {{ head }}
-  <title>{{ head_title }}</title>
-  {{ styles }}
-  {{ scripts }}
-</head>
-<body class="install-page">
-
-<div class="l-container">
-
-  <header role="banner">
-    {% if site_name or site_slogan %}
-      <div class="name-and-slogan">
-        {% if site_name %}
-          <h1>{{ site_name }}</h1>
-        {% endif %}
-        {% if site_slogan %}
-          <div class="site-slogan">{{ site_slogan }}</div>
-        {% endif %}
-      </div>{# /.name-and-slogan #}
-    {% endif %}
-  </header>
-
-  {% if sidebar_first %}
-    <aside class="l-sidebar-first" role="complementary">
-      {{ sidebar_first }}
-    </aside>{# /.l-sidebar-first #}
-  {% endif %}
-
-  <main role="main">
-    {% if title %}
-      <h1>{{ title }}</h1>
-    {% endif %}
-    {{ messages }}
-    {{ content }}
-  </main>
-
-  {% if sidebar_second %}
-    <aside class="l-sidebar-second" role="complementary">
-      {{ sidebar_second }}
-    </aside>{# /.l-sidebar-second #}
-  {% endif %}
-
-  {% if footer %}
-    <footer role="contentinfo">
-      {{ footer }}
-    </footer>
-  {% endif %}
-
-</div>{# /.l-container #}
-
-</body>
-</html>
diff --git a/core/themes/seven/templates/install-page.html.twig b/core/themes/seven/templates/install-page.html.twig
new file mode 100644
index 0000000..81ddece
--- /dev/null
+++ b/core/themes/seven/templates/install-page.html.twig
@@ -0,0 +1,69 @@
+{#
+/**
+ * @file
+ * Seven theme implementation to display a Drupal installation page.
+ *
+ * All the available variables are mirrored in html.html.twig and
+ * page.html.twig.
+ * Some may be blank but they are provided for consistency.
+ *
+ * @see template_preprocess_install_page()
+ *
+ * @ingroup themeable
+ */
+#}
+<!DOCTYPE html>
+<html{{ html_attributes }} class="install-background">
+<head>
+  {{ head }}
+  <title>{{ head_title }}</title>
+  {{ styles }}
+  {{ scripts }}
+</head>
+<body class="{{ attributes.class }}">
+
+<div class="l-container">
+
+  <header role="banner">
+    {% if site_name or site_slogan %}
+      <div class="name-and-slogan">
+        {% if site_name %}
+          <h1>{{ site_name }}</h1>
+        {% endif %}
+        {% if site_slogan %}
+          <div class="site-slogan">{{ site_slogan }}</div>
+        {% endif %}
+      </div>{# /.name-and-slogan #}
+    {% endif %}
+  </header>
+
+  {% if sidebar_first %}
+    <aside class="l-sidebar-first" role="complementary">
+      {{ sidebar_first }}
+    </aside>{# /.l-sidebar-first #}
+  {% endif %}
+
+  <main role="main">
+    {% if title %}
+      <h1>{{ title }}</h1>
+    {% endif %}
+    {{ messages }}
+    {{ content }}
+  </main>
+
+  {% if sidebar_second %}
+    <aside class="l-sidebar-second" role="complementary">
+      {{ sidebar_second }}
+    </aside>{# /.l-sidebar-second #}
+  {% endif %}
+
+  {% if footer %}
+    <footer role="contentinfo">
+      {{ footer }}
+    </footer>
+  {% endif %}
+
+</div>{# /.l-container #}
+
+</body>
+</html>
diff --git a/core/update.php b/core/update.php
index 433d5cf..c0f1139 100644
--- a/core/update.php
+++ b/core/update.php
@@ -54,13 +54,12 @@ function update_selection_page() {
   // Make sure there is no stale theme registry.
   \Drupal::cache()->deleteAll();
 
-  drupal_set_title('Drupal database update');
-  $elements = \Drupal::formBuilder()->getForm('Drupal\Core\Update\Form\UpdateScriptSelectionForm');
-  $output = drupal_render($elements);
+  $build = \Drupal::formBuilder()->getForm('Drupal\Core\Update\Form\UpdateScriptSelectionForm');
+  $build['#title'] = 'Drupal database update';
 
   update_task_list('select');
 
-  return $output;
+  return $build;
 }
 
 /**
@@ -98,7 +97,6 @@ function update_flush_all_caches() {
  * Displays results of the update script with any accompanying errors.
  */
 function update_results_page() {
-  drupal_set_title('Drupal database update');
 
   update_task_list();
   // Report end result.
@@ -178,7 +176,11 @@ function update_results_page() {
   unset($_SESSION['update_results']);
   unset($_SESSION['update_success']);
 
-  return $output;
+  $build = array(
+    '#title' => 'Drupal database update',
+    '#markup' => $output,
+  );
+  return $build;
 }
 
 /**
@@ -199,7 +201,6 @@ function update_info_page() {
   $keyvalue->get('update_available_release')->deleteAll();
 
   update_task_list('info');
-  drupal_set_title('Drupal database update');
   $token = drupal_get_token('update');
   $output = '<p>Use this utility to update your database whenever a new release of Drupal or a module is installed.</p><p>For more detailed information, see the <a href="http://drupal.org/upgrade">upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.</p>';
   $output .= "<ol>\n";
@@ -212,7 +213,12 @@ function update_info_page() {
   $form_action = check_url(drupal_current_script_url(array('op' => 'selection', 'token' => $token)));
   $output .= '<form method="post" action="' . $form_action . '"><p><input type="submit" value="Continue" class="form-submit button button-primary" /></p></form>';
   $output .= "\n";
-  return $output;
+
+  $build = array(
+    '#title' => 'Drupal database update',
+    '#markup' => $output,
+  );
+  return $build;
 }
 
 /**
@@ -225,14 +231,19 @@ function update_access_denied_page() {
   drupal_add_http_header('Status', '403 Forbidden');
   header(\Drupal::request()->server->get('SERVER_PROTOCOL') . ' 403 Forbidden');
   watchdog('access denied', 'update.php', NULL, WATCHDOG_WARNING);
-  drupal_set_title('Access denied');
-  return '<p>Access denied. You are not authorized to access this page. Log in using either an account with the <em>administer software updates</em> permission or the site maintenance account (the account you created during installation). If you cannot log in, you will have to edit <code>settings.php</code> to bypass this access check. To do this:</p>
+  $output = '<p>Access denied. You are not authorized to access this page. Log in using either an account with the <em>administer software updates</em> permission or the site maintenance account (the account you created during installation). If you cannot log in, you will have to edit <code>settings.php</code> to bypass this access check. To do this:</p>
 <ol>
  <li>With a text editor find the settings.php file on your system. From the main Drupal directory that you installed all the files into, go to <code>sites/your_site_name</code> if such directory exists, or else to <code>sites/default</code> which applies otherwise.</li>
  <li>There is a line inside your settings.php file that says <code>$settings[\'update_free_access\'] = FALSE;</code>. Change it to <code>$settings[\'update_free_access\'] = TRUE;</code>.</li>
  <li>As soon as the update.php script is done, you must change the settings.php file back to its original form with <code>$settings[\'update_free_access\'] = FALSE;</code>.</li>
  <li>To avoid having this problem in the future, remember to log in to your website using either an account with the <em>administer software updates</em> permission or the site maintenance account (the account you created during installation) before you backup your database at the beginning of the update process.</li>
 </ol>';
+
+  $build = array(
+    '#title' => 'Access denied',
+    '#markup' => $output,
+  );
+  return $build;
 }
 
 /**
@@ -452,9 +463,16 @@ function update_task_list($active = NULL) {
     drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
     $maintenance_page = array(
       '#theme' => 'maintenance_page',
-      '#content' => $output,
+      // $output has to be rendered here, because the maintenance page template
+      // is not wrapped into the html template, which means that any #attached
+      // libraries in $output will not be loaded, because the wrapping HTML has
+      // been printed already.
+      '#content' => drupal_render($output),
       '#show_messages' => !$progress_page,
     );
+    if (isset($output['#title'])) {
+      $maintenance_page['#page']['#title'] = $output['#title'];
+    }
     print drupal_render($maintenance_page);
   }
 }
-- 
1.8.4.2

