diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index bc44213..9476fcf 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -565,9 +565,15 @@ function _theme($hook, $variables = array()) {
     }
   }
   if (isset($info['preprocess functions'])) {
+    $context = array(
+      'hook' => $hook,
+      'suggestions' => $suggestions,
+      'suggestion' => isset($suggestion) ? $suggestion : NULL,
+      'info' => $info,
+    );
     foreach ($info['preprocess functions'] as $preprocessor_function) {
       if (function_exists($preprocessor_function)) {
-        $preprocessor_function($variables, $hook, $info);
+        $preprocessor_function($variables, $context);
       }
     }
   }
@@ -609,7 +615,13 @@ function _theme($hook, $variables = array()) {
     // new variable to track that.
     if (!isset($variables['directory'])) {
       $default_template_variables = array();
-      template_preprocess($default_template_variables, $hook, $info);
+      $context = array(
+        'hook' => $hook,
+        'suggestions' => $suggestions,
+        'suggestion' => isset($suggestion) ? $suggestion : NULL,
+        'info' => $info,
+      );
+      template_preprocess($default_template_variables, $context);
       $variables += $default_template_variables;
     }
     if (!isset($default_attributes)) {
@@ -1854,7 +1866,7 @@ function template_preprocess_container(&$variables) {
  *
  * @see _theme()
  */
-function template_preprocess(&$variables, $hook, $info) {
+function template_preprocess(&$variables, $context) {
   // Tell all templates where they are located.
   $variables['directory'] = path_to_theme();
 
@@ -1873,8 +1885,8 @@ function template_preprocess(&$variables, $hook, $info) {
 
   // When theming a render element, merge its #attributes into
   // $variables['attributes'].
-  if (isset($info['render element'])) {
-    $key = $info['render element'];
+  if (isset($context['info']['render element'])) {
+    $key = $context['info']['render element'];
     if (isset($variables[$key]['#attributes'])) {
       $variables['attributes'] = NestedArray::mergeDeep($variables['attributes'], $variables[$key]['#attributes']);
     }
@@ -2075,7 +2087,7 @@ function template_preprocess_html(&$variables) {
  *
  * @see drupal_render_page()
  */
-function template_preprocess_page(&$variables) {
+function template_preprocess_page(&$variables, $context) {
   $language_interface = \Drupal::languageManager()->getCurrentLanguage();
   $site_config = \Drupal::config('system.site');
 
@@ -2166,6 +2178,11 @@ function template_preprocess_page(&$variables) {
       '#breadcrumb' => \Drupal::service('breadcrumb')->build(\Drupal::request()->attributes->all()),
     );
   }
+
+  // @todo Remove once https://drupal.org/node/939462 is fixed.
+  if ($context['suggestion'] !== 'page' && function_exists($function = 'template_preprocess_' . $context['suggestion'])) {
+    $function($variables, $context);
+  }
 }
 
 /**
@@ -2249,10 +2266,7 @@ function theme_get_suggestions($args, $base, $delimiter = '__') {
  *
  * @see system_page_build()
  */
-function template_preprocess_maintenance_page(&$variables) {
-  // @todo Rename the templates to page--maintenance + page--install.
-  template_preprocess_page($variables);
-
+function template_preprocess_page__maintenance(&$variables) {
   $page_object = $variables['page']['#page'];
   $attributes = $page_object->getBodyAttributes();
   $classes = $attributes['class'];
@@ -2284,11 +2298,9 @@ function template_preprocess_maintenance_page(&$variables) {
  *   An associative array containing:
  *   - content - An array of page content.
  *
- * @see template_preprocess_maintenance_page()
+ * @see template_preprocess_page__maintenance()
  */
-function template_preprocess_install_page(&$variables) {
-  template_preprocess_maintenance_page($variables);
-
+function template_preprocess_page__maintenance__install(&$variables) {
   $page_object = $variables['page']['#page'];
   $attributes = $page_object->getBodyAttributes();
   $classes = $attributes['class'];
@@ -2339,7 +2351,7 @@ function template_preprocess_region(&$variables) {
  *   - content_attributes: A string containing the attributes for the content's
  *     div.
  */
-function template_preprocess_field(&$variables, $hook) {
+function template_preprocess_field(&$variables) {
   $element = $variables['element'];
 
   $variables['label_hidden'] = ($element['#label_display'] == 'hidden');
@@ -2512,6 +2524,16 @@ function drupal_common_theme() {
       'render element' => 'page',
       'template' => 'page',
     ),
+    'page__maintenance' => array(
+      'base hook' => 'page',
+      'render element' => 'page',
+      'template' => 'page--maintenance',
+    ),
+    'page__maintenance__install' => array(
+      'base hook' => 'page',
+      'render element' => 'page',
+      'template' => 'page--maintenance--install',
+    ),
     'region' => array(
       'render element' => 'elements',
       'template' => 'region',
@@ -2582,14 +2604,6 @@ function drupal_common_theme() {
       'variables' => array('size' => 1),
     ),
     // From theme.maintenance.inc.
-    'maintenance_page' => array(
-      'render element' => 'page',
-      'template' => 'maintenance-page',
-    ),
-    'install_page' => array(
-      'render element' => 'page',
-      'template' => 'install-page',
-    ),
     'task_list' => array(
       'variables' => array('items' => NULL, 'active' => NULL,  'variant' => NULL),
     ),
diff --git a/core/lib/Drupal/Core/Page/DefaultHtmlPageRenderer.php b/core/lib/Drupal/Core/Page/DefaultHtmlPageRenderer.php
index 4358ead..d6ff5d4 100644
--- a/core/lib/Drupal/Core/Page/DefaultHtmlPageRenderer.php
+++ b/core/lib/Drupal/Core/Page/DefaultHtmlPageRenderer.php
@@ -72,8 +72,7 @@ public static function renderPage($main, $title = '', $theme = 'maintenance', ar
     $page = new HtmlPage('', array(), $title);
     $page_array = array(
       '#type' => 'page',
-      // @todo Change into theme suggestions "page__$theme".
-      '#theme' => $theme . '_page',
+      '#theme' => 'page__maintenance' . ($theme !== 'maintenance' ? "__$theme" : ''),
       '#title' => $title,
       'content' => array(
         'system_main' => $main,
diff --git a/core/modules/contextual/contextual.module b/core/modules/contextual/contextual.module
index a04792f..e932a26 100644
--- a/core/modules/contextual/contextual.module
+++ b/core/modules/contextual/contextual.module
@@ -120,14 +120,14 @@ function contextual_element_info() {
  * @see contextual_page_build()
  * @see \Drupal\contextual\ContextualController::render()
  */
-function contextual_preprocess(&$variables, $hook, $info) {
+function contextual_preprocess(&$variables, $context) {
   // Determine the primary theme function argument.
-  if (!empty($info['variables'])) {
-    $keys = array_keys($info['variables']);
+  if (!empty($context['info']['variables'])) {
+    $keys = array_keys($context['info']['variables']);
     $key = $keys[0];
   }
-  elseif (!empty($info['render element'])) {
-    $key = $info['render element'];
+  elseif (!empty($context['info']['render element'])) {
+    $key = $context['info']['render element'];
   }
   if (!empty($key) && isset($variables[$key])) {
     $element = $variables[$key];
diff --git a/core/modules/system/templates/install-page.html.twig b/core/modules/system/templates/page--maintenance--install.html.twig
similarity index 100%
rename from core/modules/system/templates/install-page.html.twig
rename to core/modules/system/templates/page--maintenance--install.html.twig
diff --git a/core/modules/system/templates/maintenance-page.html.twig b/core/modules/system/templates/page--maintenance.html.twig
similarity index 100%
rename from core/modules/system/templates/maintenance-page.html.twig
rename to core/modules/system/templates/page--maintenance.html.twig
diff --git a/core/modules/system/theme.api.php b/core/modules/system/theme.api.php
index 50fa74e..493a975 100644
--- a/core/modules/system/theme.api.php
+++ b/core/modules/system/theme.api.php
@@ -155,29 +155,32 @@ function hook_form_system_theme_settings_alter(&$form, &$form_state) {
  *
  * @param $variables
  *   The variables array (modify in place).
- * @param $hook
- *   The name of the theme hook.
+ * @param $context
+ *   The context for which this preprocess function is running. This includes:
+ *   - hook:
+ *     @todo Document this after figuring out what it is. It's not necessarily
+ *       the same as what was passed to _theme() as $hook, nor is it
+ *       necessarily the same as 'suggestion'. What is it?
+ *   - suggestions: The array of theme hook suggestions that were searched in
+ *     order to find one that was implemented via a template or function.
+ *   - suggestion: The theme hook suggestion that was found and therefore
+ *     will be rendered after variable processing is complete.
+ *   - info: The theme registry information for the matching hook/suggestion.
  */
-function hook_preprocess(&$variables, $hook) {
- static $hooks;
-
+function hook_preprocess(&$variables, $context) {
   // Add contextual links to the variables, if the user has permission.
 
   if (!user_access('access contextual links')) {
     return;
   }
 
-  if (!isset($hooks)) {
-    $hooks = theme_get_registry();
-  }
-
   // Determine the primary theme function argument.
-  if (isset($hooks[$hook]['variables'])) {
-    $keys = array_keys($hooks[$hook]['variables']);
+  if (isset($context['info']['variables'])) {
+    $keys = array_keys($context['info']['variables']);
     $key = $keys[0];
   }
   else {
-    $key = $hooks[$hook]['render element'];
+    $key = $context['info']['render element'];
   }
 
   if (isset($variables[$key])) {
diff --git a/core/themes/bartik/bartik.theme b/core/themes/bartik/bartik.theme
index 1532b9f..5fee2d8 100644
--- a/core/themes/bartik/bartik.theme
+++ b/core/themes/bartik/bartik.theme
@@ -14,7 +14,7 @@ use Drupal\Core\Template\Attribute;
  *
  * Adds body classes if certain regions have content.
  */
-function bartik_preprocess_page(&$variables) {
+function bartik_preprocess_page(&$variables, $context) {
   // Add information about the number of sidebars.
   /** @var \Drupal\Core\Page\HtmlPage $page_object */
   $page_object = $variables['page']['#page'];
@@ -88,12 +88,17 @@ function bartik_preprocess_page(&$variables) {
     // Make sure the shortcut link is the first item in title_suffix.
     $variables['title_suffix']['add_or_remove_shortcut']['#weight'] = -100;
   }
+
+  // @todo Remove once https://drupal.org/node/939462 is fixed.
+  if ($context['suggestion'] !== 'page' && function_exists($function = 'bartik_preprocess_' . $context['suggestion'])) {
+    $function($variables, $context);
+  }
 }
 
 /**
- * Implements hook_preprocess_HOOK() for maintenance-page.html.twig.
+ * Implements hook_preprocess_HOOK().
  */
-function bartik_preprocess_maintenance_page(&$variables) {
+function bartik_preprocess_page__maintenance(&$variables) {
   // By default, site_name is set to Drupal if no db connection is available
   // or during site installation. Setting site_name to an empty string makes
   // the site and update pages look cleaner.
diff --git a/core/themes/bartik/templates/maintenance-page.html.twig b/core/themes/bartik/templates/page--maintenance.html.twig
similarity index 100%
rename from core/themes/bartik/templates/maintenance-page.html.twig
rename to core/themes/bartik/templates/page--maintenance.html.twig
diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme
index c3e7a32..a4d0eb1 100644
--- a/core/themes/seven/seven.theme
+++ b/core/themes/seven/seven.theme
@@ -12,7 +12,7 @@ use Drupal\Component\Utility\String;
 /**
  * Implements hook_preprocess_HOOK() for page templates.
  */
-function seven_preprocess_page(&$variables) {
+function seven_preprocess_page(&$variables, $context) {
   /** @var \Drupal\Core\Page\HtmlPage $page_object */
   $page_object = $variables['page']['#page'];
   $attributes = $page_object->getBodyAttributes();
@@ -34,6 +34,11 @@ function seven_preprocess_page(&$variables) {
     '#theme' => 'menu_local_tasks',
     '#secondary' => isset($variables['tabs']['#secondary']) ? $variables['tabs']['#secondary'] : '',
   );
+
+  // @todo Remove once https://drupal.org/node/939462 is fixed.
+  if ($context['suggestion'] !== 'page' && function_exists($function = 'seven_preprocess_' . $context['suggestion'])) {
+    $function($variables, $context);
+  }
 }
 
 /**
@@ -267,9 +272,9 @@ function seven_element_info_alter(&$type) {
 }
 
 /**
- * Implements hook_preprocess_install_page().
+ * Implements hook_preprocess_HOOK().
  */
-function seven_preprocess_install_page(&$variables) {
+function seven_preprocess_page__maintenance__install(&$variables) {
   $page_object = $variables['page']['#page'];
   $attributes = $page_object->getHtmlAttributes();
   $classes = $attributes['class'];
@@ -290,9 +295,9 @@ function seven_preprocess_install_page(&$variables) {
 }
 
 /**
- * Implements hook_preprocess_maintenance_page().
+ * Implements hook_preprocess_HOOK().
  */
-function seven_preprocess_maintenance_page(&$variables) {
+function seven_preprocess_page__maintenance(&$variables) {
   $page_object = $variables['page']['#page'];
   $attributes = $page_object->getHtmlAttributes();
   $classes = $attributes['class'];
diff --git a/core/themes/seven/templates/install-page.html.twig b/core/themes/seven/templates/page--maintenance--install.html.twig
similarity index 100%
rename from core/themes/seven/templates/install-page.html.twig
rename to core/themes/seven/templates/page--maintenance--install.html.twig
diff --git a/core/themes/seven/templates/maintenance-page.html.twig b/core/themes/seven/templates/page--maintenance.html.twig
similarity index 100%
rename from core/themes/seven/templates/maintenance-page.html.twig
rename to core/themes/seven/templates/page--maintenance.html.twig
