diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index c63f6790ef9488738333fe655b999ded899a87ee..ed5bd5846b5d0738e3a776c03367a3c23ccba89c 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1925,6 +1925,22 @@ function drupal_pre_render_html(array $element) {
 
   return $element;
 }
+
+/**
+ * #pre_render callback for the page element type.
+ *
+ * @param array $element
+ *   A structured array containing the page element type build properties.
+ *
+ * @see system_element_info()
+ */
+function drupal_pre_render_page(array $element) {
+  global $theme;
+  $element['#cache']['tags']['theme'] = $theme;
+  $element['#cache']['tags']['theme_global_settings'] = TRUE;
+  return $element;
+}
+
 /**
  * Prepares variables for HTML document templates.
  *
diff --git a/core/lib/Drupal/Core/Cache/LanguageCacheContext.php b/core/lib/Drupal/Core/Cache/LanguageCacheContext.php
index b73652f8f76b6da907edf68dc6ac56961040f8f4..e2c1bd93bc277a80844e67f890802d9e2e5f3785 100644
--- a/core/lib/Drupal/Core/Cache/LanguageCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/LanguageCacheContext.php
@@ -43,9 +43,9 @@ public static function getLabel() {
    */
   public function getContext() {
     $context_parts = array();
-    if ($this->language_manager->isMultilingual()) {
-      foreach ($this->language_manager->getLanguageTypes() as $type) {
-        $context_parts[] = $this->language_manager->getCurrentLanguage($type)->id;
+    if ($this->languageManager->isMultilingual()) {
+      foreach ($this->languageManager->getLanguageTypes() as $type) {
+        $context_parts[] = $this->languageManager->getCurrentLanguage($type)->id;
       }
     }
     return implode(':', $context_parts);
diff --git a/core/modules/block/lib/Drupal/block/BlockViewBuilder.php b/core/modules/block/lib/Drupal/block/BlockViewBuilder.php
index a6a925afb6c69a6f98d6969332cf73bf08071ed9..cb448515352d9a4905a6c60613e10ef0cdff363b 100644
--- a/core/modules/block/lib/Drupal/block/BlockViewBuilder.php
+++ b/core/modules/block/lib/Drupal/block/BlockViewBuilder.php
@@ -74,6 +74,7 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la
         'content' => TRUE,
         'block_view' => TRUE,
         'block' => array($entity->id()),
+        'theme' => $entity->get('theme'),
       );
       $build[$entity_id]['#cache']['tags'] = NestedArray::mergeDeep($default_cache_tags, $plugin->getCacheTags());
 
diff --git a/core/modules/block/lib/Drupal/block/Entity/Block.php b/core/modules/block/lib/Drupal/block/Entity/Block.php
index 9e171711c8dc13dd0ffa394fd07156cd889b0fce..aa9141c11c9a14f8f5e3a5ac5b062516c7a4342d 100644
--- a/core/modules/block/lib/Drupal/block/Entity/Block.php
+++ b/core/modules/block/lib/Drupal/block/Entity/Block.php
@@ -160,8 +160,7 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) {
     // When placing a new block, invalidate all cache entries for this theme,
     // since any page that uses this theme might be affected.
     else {
-      // @todo Replace with theme cache tag: https://drupal.org/node/2185617
-      Cache::invalidateTags(array('content' => TRUE));
+      Cache::invalidateTags(array('theme' => $this->theme));
     }
   }
 
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php
index 4fa4e63a43dace09c619d5eaa62f6d5c9a637afb..37074ccb619a14308cbefe3bef15e4d2b4210b30 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php
@@ -281,6 +281,8 @@ public function testBlockCacheTags() {
     $cid = sha1(implode(':', $cid_parts));
     $cache_entry = \Drupal::cache('render')->get($cid);
     $expected_cache_tags = array(
+      'theme:stark',
+      'theme_global_settings:1',
       'content:1',
       'block_view:1',
       'block:powered',
@@ -288,6 +290,13 @@ public function testBlockCacheTags() {
     );
     $this->assertIdentical($cache_entry->tags, $expected_cache_tags);
     $cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:en:stark');
+    $expected_cache_tags = array(
+      'content:1',
+      'block_view:1',
+      'block:powered',
+      'theme:stark',
+      'block_plugin:system_powered_by_block',
+    );
     $this->assertIdentical($cache_entry->tags, $expected_cache_tags);
 
     // The "Powered by Drupal" block is modified; verify a cache miss.
@@ -312,6 +321,8 @@ public function testBlockCacheTags() {
     $cid = sha1(implode(':', $cid_parts));
     $cache_entry = \Drupal::cache('render')->get($cid);
     $expected_cache_tags = array(
+      'theme:stark',
+      'theme_global_settings:1',
       'content:1',
       'block_view:1',
       'block:powered-2',
@@ -323,6 +334,7 @@ public function testBlockCacheTags() {
       'content:1',
       'block_view:1',
       'block:powered',
+      'theme:stark',
       'block_plugin:system_powered_by_block',
     );
     $cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:en:stark');
@@ -331,6 +343,7 @@ public function testBlockCacheTags() {
       'content:1',
       'block_view:1',
       'block:powered-2',
+      'theme:stark',
       'block_plugin:system_powered_by_block',
     );
     $cache_entry = \Drupal::cache('render')->get('entity_view:block:powered-2:en:stark');
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php
index 63bbf45cb792f82b2408baa5e8abe01a1231783d..d4066b097d4a69f916b1bb5849aec975a14938e3 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php
@@ -221,7 +221,7 @@ public function testBlockViewBuilderAlter() {
     $this->container->get('request')->setMethod('GET');
 
     $default_keys = array('entity_view', 'block', 'test_block', 'en', 'cache_context.theme');
-    $default_tags = array('content' => TRUE, 'block_view' => TRUE, 'block' => array('test_block'), 'block_plugin' => array('test_cache'));
+    $default_tags = array('content' => TRUE, 'block_view' => TRUE, 'block' => array('test_block'), 'theme' => 'stark', 'block_plugin' => array('test_cache'));
 
     // Advanced: cached block, but an alter hook adds an additional cache key.
     $this->setBlockCacheConfig(array(
@@ -236,7 +236,7 @@ public function testBlockViewBuilderAlter() {
     $this->assertIdentical(drupal_render($build), '');
     $cache_entry = $this->container->get('cache.render')->get($cid);
     $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
-    $expected_flattened_tags = array('content:1', 'block_view:1', 'block:test_block', 'block_plugin:test_cache');
+    $expected_flattened_tags = array('content:1', 'block_view:1', 'block:test_block', 'theme:stark', 'block_plugin:test_cache');
     $this->assertIdentical($cache_entry->tags, array_combine($expected_flattened_tags, $expected_flattened_tags)); //, 'The block render element has been cached with the expected cache tags.');
     $this->container->get('cache.render')->delete($cid);
 
@@ -250,7 +250,7 @@ public function testBlockViewBuilderAlter() {
     $this->assertIdentical(drupal_render($build), '');
     $cache_entry = $this->container->get('cache.render')->get($cid);
     $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
-    $expected_flattened_tags = array('content:1', 'block_view:1', 'block:test_block', 'block_plugin:test_cache', $alter_add_tag . ':1');
+    $expected_flattened_tags = array('content:1', 'block_view:1', 'block:test_block', 'theme:stark', 'block_plugin:test_cache', $alter_add_tag . ':1');
     $this->assertIdentical($cache_entry->tags, array_combine($expected_flattened_tags, $expected_flattened_tags)); //, 'The block render element has been cached with the expected cache tags.');
     $this->container->get('cache.render')->delete($cid);
 
diff --git a/core/modules/menu/lib/Drupal/menu/Tests/MenuCacheTagsTest.php b/core/modules/menu/lib/Drupal/menu/Tests/MenuCacheTagsTest.php
index 089c2ada55755d532ff7c1a53fc79efd1eb6a775..fec45d043bfbf07db71b2bd80df558f31d62f0ee 100644
--- a/core/modules/menu/lib/Drupal/menu/Tests/MenuCacheTagsTest.php
+++ b/core/modules/menu/lib/Drupal/menu/Tests/MenuCacheTagsTest.php
@@ -59,6 +59,8 @@ public function testMenuBlock() {
 
     // Verify a cache hit, but also the presence of the correct cache tags.
     $expected_tags = array(
+      'theme:stark',
+      'theme_global_settings:1',
       'content:1',
       'block_view:1',
       'block:' . $block->id(),
@@ -117,7 +119,7 @@ public function testMenuBlock() {
     $this->verifyPageCache($path, 'MISS');
 
     // Verify a cache hit.
-    $this->verifyPageCache($path, 'HIT', array('content:1'));
+    $this->verifyPageCache($path, 'HIT', array('content:1', 'theme:stark', 'theme_global_settings:1'));
   }
 
 }
diff --git a/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php b/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php
index 2cce3b8a940b97a4f2c35788ea67a764b4ccf087..9c605e9bd318c26a78fa1e5d3533ba0781874b47 100644
--- a/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php
+++ b/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php
@@ -419,6 +419,16 @@ public function submitForm(array &$form, array &$form_state) {
 
     theme_settings_convert_to_config($values, $config)->save();
 
+    // Invalidate either the theme-specific cache tag or the global theme
+    // settings cache tag, depending on whose settings were actually changed.
+    if (isset($values['theme'])) {
+      Cache::invalidateTags(array('theme' => $values['theme']));
+    }
+    else {
+      Cache::invalidateTags(array('theme_global_settings' => TRUE));
+    }
+
+    // @todo Remove this in https://drupal.org/node/2124957.
     Cache::invalidateTags(array('content' => TRUE));
   }
 
diff --git a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemBrandingBlock.php b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemBrandingBlock.php
index 0b3213902711aff88a9b43c362ee13b8f6038a92..e9555b79b1449c9b5196f94a22e4921d9f207fe8 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemBrandingBlock.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemBrandingBlock.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Plugin\Block;
 
+use Drupal\Component\Utility\NestedArray;
 use Drupal\block\BlockBase;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
@@ -44,7 +45,7 @@ class SystemBrandingBlock extends BlockBase implements ContainerFactoryPluginInt
    *
    * @var \Drupal\Core\Session\AccountInterface
    */
-  protected $current_user;
+  protected $currentUser;
 
   /**
    * Creates a SystemBrandingBlock instance.
@@ -92,6 +93,12 @@ public function defaultConfiguration() {
       'use_site_name' => TRUE,
       'use_site_slogan' => TRUE,
       'label_display' => FALSE,
+      // Modify the default max age for the 'Site branding' block: the site
+      // logo, name and slogan are static for a given language, except when the
+      // theme settings are updated (global theme settings or theme-specific
+      // settings). Cache tags for those cases ensure that a cached version of
+      // this block is invalidated automatically.
+      'cache' => array('max_age' => \Drupal\Core\Cache\Cache::PERMANENT),
     );
   }
 
@@ -108,12 +115,12 @@ public function blockForm($form, &$form_state) {
 
     if ($administer_themes_access) {
       // Get paths to theme settings pages.
-      $appearance_url = $this->urlGenerator->generateFromRoute('system.themes_page');
+      $appearance_settings_url = $this->urlGenerator->generateFromRoute('system.theme_settings');
       $theme_settings_url = $this->urlGenerator->generateFromRoute('system.theme_settings_theme', array('theme' => $theme));
 
-      // Provide links to the Appearance and Theme Settings pages
+      // Provide links to the Appearance Settings and Theme Settings pages
       // if the user has access to administer themes.
-      $site_logo_description = $this->t('Defined on the <a href="@appearance">Appearance</a> or <a href="@theme">Theme Settings</a> page.', array('@appearance' => $appearance_url, '@theme' => $theme_settings_url));
+      $site_logo_description = $this->t('Defined on the <a href="@appearance">Appearance Settings</a> or <a href="@theme">Theme Settings</a> page.', array('@appearance' => $appearance_settings_url, '@theme' => $theme_settings_url));
     }
     else {
       // Explain that the user does not have access to the Appearance and Theme
@@ -200,4 +207,29 @@ public function build() {
     return $build;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags() {
+    // The theme-specific cache tag is set automatically for each block, but the
+    // output of this block also depends on the global theme settings.
+    $tags = array(
+      'theme_global_setting' => TRUE,
+    );
+    return NestedArray::mergeDeep(parent::getCacheTags(), $tags);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getRequiredCacheContexts() {
+    // The 'Site branding' block must be cached per theme and per language: the
+    // site logo, name and slogan are defined on a per-theme basis, and the name
+    // and slogan may be translated.
+    // We don't need to return 'cache_context.theme' also, because that cache
+    // context is automatically applied to all blocks.
+    // @see \Drupal\block\BlockViewBuilder::viewMultiple()
+    return array('cache_context.language');
+  }
+
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php b/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php
index 029bcb81a7668420d3c3c49ee31908db22091f52..7f6b6a0fe0848b80dd83dcd4d8962c5c8e0e235f 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Bootstrap/PageCacheTest.php
@@ -66,7 +66,14 @@ function testPageCacheTags() {
     $cid = sha1(implode(':', $cid_parts));
     $cache_entry = \Drupal::cache('render')->get($cid);
     sort($cache_entry->tags);
-    $this->assertIdentical($cache_entry->tags, array('content:1', 'pre_render:1', 'system_test_cache_tags_page:1'));
+    $expected_tags = array(
+      'content:1',
+      'pre_render:1',
+      'system_test_cache_tags_page:1',
+      'theme:stark',
+      'theme_global_settings:1',
+    );
+    $this->assertIdentical($cache_entry->tags, $expected_tags);
 
     Cache::invalidateTags($tags);
     $this->drupalGet($path);
diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php b/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php
index 7e9d1d577d5060a58fe7e6177872686073de6915..f9f76ac291b94c49e496478f2d90aa287f1d6df0 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Cache/PageCacheTagsIntegrationTest.php
@@ -77,6 +77,8 @@ function testPageCacheTags() {
     // Full node page 1.
     $this->verifyPageCacheTags('node/' . $node_1->id(), array(
       'content:1',
+      'theme:bartik',
+      'theme_global_settings:1',
       'block_view:1',
       'block:bartik_content',
       'block:bartik_tools',
@@ -100,6 +102,8 @@ function testPageCacheTags() {
     // Full node page 2.
     $this->verifyPageCacheTags('node/' . $node_2->id(), array(
       'content:1',
+      'theme:bartik',
+      'theme_global_settings:1',
       'block_view:1',
       'block:bartik_content',
       'block:bartik_tools',
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php
index e1f66b5e3e725f369a86cea4d8ce706deb80f02d..5112cc2a1ae349a59893a41a6b0820065f5b0217 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php
@@ -210,6 +210,8 @@ public function testReferencedEntity() {
     $non_referencing_entity_path = $this->non_referencing_entity->getSystemPath();
     $listing_path = 'entity_test/list/' . $entity_type . '_reference/' . $entity_type . '/' . $this->entity->id();
 
+    $theme_cache_tags = array('content:1', 'theme:stark', 'theme_global_settings:1');
+
     // Generate the standardized entity cache tags.
     $cache_tag = $entity_type . ':' . $this->entity->id();
     $view_cache_tag = $entity_type . '_view:1';
@@ -232,7 +234,7 @@ public function testReferencedEntity() {
     $this->verifyPageCache($referencing_entity_path, 'MISS');
 
     // Verify a cache hit, but also the presence of the correct cache tags.
-    $tags = array_merge(array('content:1'), $referencing_entity_cache_tags);
+    $tags = array_merge($theme_cache_tags, $referencing_entity_cache_tags);
     $this->verifyPageCache($referencing_entity_path, 'HIT', $tags);
 
     // Also verify the existence of an entity render cache entry.
@@ -245,7 +247,7 @@ public function testReferencedEntity() {
     $this->verifyPageCache($non_referencing_entity_path, 'MISS');
 
     // Verify a cache hit, but also the presence of the correct cache tags.
-    $tags = array_merge(array('content:1'), $non_referencing_entity_cache_tags);
+    $tags = array_merge($theme_cache_tags, $non_referencing_entity_cache_tags);
     $this->verifyPageCache($non_referencing_entity_path, 'HIT', $tags);
 
     // Also verify the existence of an entity render cache entry.
@@ -259,7 +261,7 @@ public function testReferencedEntity() {
     $this->verifyPageCache($listing_path, 'MISS');
 
     // Verify a cache hit, but also the presence of the correct cache tags.
-    $tags = array_merge(array('content:1'), $referencing_entity_cache_tags);
+    $tags = array_merge($theme_cache_tags, $referencing_entity_cache_tags);
     $this->verifyPageCache($listing_path, 'HIT', $tags);
 
 
@@ -407,13 +409,12 @@ public function testReferencedEntity() {
     $this->verifyPageCache($non_referencing_entity_path, 'HIT');
 
     // Verify cache hits.
-    $tags = array(
-      'content:1',
+    $tags = array_merge($theme_cache_tags, array(
       'entity_test_view:1',
       'entity_test:' . $this->referencing_entity->id(),
-    );
+    ));
     $this->verifyPageCache($referencing_entity_path, 'HIT', $tags);
-    $this->verifyPageCache($listing_path, 'HIT', array('content:1'));
+    $this->verifyPageCache($listing_path, 'HIT', $theme_cache_tags);
   }
 
 }
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index a870cb3ab99d54c94fe904c16cdd3129a19f3617..4b6a1654751d5109deb13a05f6fe1c8b5b3b2da2 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -290,6 +290,7 @@ function system_element_info() {
   );
   $types['page'] = array(
     '#show_messages' => TRUE,
+    '#pre_render' => array('drupal_pre_render_page'),
     '#theme' => 'page',
     '#title' => '',
   );