From 5859ca2cf238b4741b122acce31f61fb644afa3a Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Thu, 15 Jan 2015 09:36:26 +0000
Subject: [PATCH] Issue #2040135 by Wim Leers, Berdir: Caches dependent on
 simple config are only invalidated on form submissions

---
 core/core.services.yml                        |   2 +-
 core/includes/module.inc                      |   2 +-
 core/lib/Drupal/Core/Access/AccessManager.php |   2 +-
 core/lib/Drupal/Core/Access/AccessResult.php  |  14 +++
 core/lib/Drupal/Core/Cache/Cache.php          |   6 +-
 core/lib/Drupal/Core/Config/Config.php        |   5 +
 core/lib/Drupal/Core/Config/ConfigBase.php    |  11 ++
 core/lib/Drupal/Core/Config/ConfigFactory.php |   2 +
 .../Core/Config/Entity/ConfigEntityBase.php   |  30 +++++
 .../Core/Config/Entity/ConfigEntityType.php   |   6 +
 .../Drupal/Core/Config/StorableConfigBase.php |  10 +-
 core/lib/Drupal/Core/Entity/Entity.php        |   2 +-
 .../Drupal/Core/Entity/EntityInterface.php    |   2 +-
 core/lib/Drupal/Core/Menu/MenuLinkTree.php    |   4 +-
 core/lib/Drupal/Core/Menu/MenuTreeStorage.php |  25 +++--
 .../Core/Menu/StaticMenuLinkOverrides.php     |   7 ++
 .../Menu/StaticMenuLinkOverridesInterface.php |   8 ++
 core/lib/Drupal/Core/Render/Element/Page.php  |  18 ---
 .../Drupal/Core/Theme/ThemeAccessCheck.php    |   4 +-
 core/lib/Drupal/Core/Theme/ThemeSettings.php  |   8 ++
 core/modules/block/src/Entity/Block.php       |  13 ---
 .../DisplayVariant/BlockPageVariant.php       |  21 +++-
 core/modules/block/src/Tests/BlockTest.php    |  21 ++--
 .../block/src/Tests/BlockViewBuilderTest.php  |   6 +-
 .../DisplayVariant/BlockPageVariantTest.php   |  24 +++-
 .../src/Tests/BlockContentCacheTagsTest.php   |   2 +-
 .../src/Tests/CommentCacheTagsTest.php        |   2 +-
 .../CommentDefaultFormatterCacheTagsTest.php  |   2 +-
 .../contact/src/Access/ContactPageAccess.php  |   4 +-
 .../filter/src/Tests/FilterAPITest.php        |   2 +-
 .../image/src/Tests/ImageFieldDisplayTest.php |   2 +-
 .../src/Config/LanguageConfigOverride.php     |   7 ++
 .../Config/LanguageConfigOverrideTest.php     | 104 ++++++++++++++++++
 .../menu_ui/src/Tests/MenuCacheTagsTest.php   |   9 +-
 .../Tests/ResponsiveImageFieldDisplayTest.php |   8 +-
 .../src/Tests/SearchPageCacheTagsTest.php     |   8 +-
 core/modules/shortcut/src/Entity/Shortcut.php |   2 +-
 .../src/Tests/ShortcutCacheTagsTest.php       |   4 +-
 .../EventSubscriber/ThemeSettingsCacheTag.php |  79 +++++++++++++
 .../system/src/Form/ThemeSettingsForm.php     |   9 --
 .../src/Plugin/Block/SystemMenuBlock.php      |   2 +-
 .../src/Tests/Bootstrap/PageCacheTest.php     |   2 -
 .../Cache/PageCacheTagsIntegrationTest.php    |  56 +++++-----
 .../Tests/Entity/EntityCacheTagsTestBase.php  |  51 ++++-----
 .../src/Tests/Menu/MenuTreeStorageTest.php    |   4 +-
 core/modules/system/system.services.yml       |   5 +
 .../src/Tests/ToolbarAdminMenuTest.php        |  14 +--
 core/modules/toolbar/toolbar.module           |   2 +-
 .../tour/src/Tests/TourCacheTagsTest.php      |  13 ++-
 .../user/src/Access/RegisterAccessCheck.php   |   4 +-
 core/modules/user/src/PermissionsHash.php     |   2 +-
 .../user/src/Tests/UserPictureTest.php        |   6 -
 .../user/src/Tests/UserSignatureTest.php      |   2 +-
 .../views/src/Plugin/ViewsHandlerManager.php  |   2 +-
 .../views/src/Plugin/ViewsPluginManager.php   |   2 +-
 .../Plugin/views/cache/CachePluginBase.php    |  10 +-
 .../views/display/DisplayPluginBase.php       |   3 +-
 core/modules/views/src/ViewsData.php          |   2 +-
 core/modules/views/views.module               |   6 +-
 core/modules/views_ui/src/ViewUI.php          |   2 +-
 .../Tests/Core/Access/AccessManagerTest.php   |   2 +-
 .../Tests/Core/Access/AccessResultTest.php    |  13 +++
 .../Drupal/Tests/Core/Cache/CacheTest.php     |   6 +-
 .../Tests/Core/Config/ConfigFactoryTest.php   |  91 +++++++++++++++
 .../Drupal/Tests/Core/Config/ConfigTest.php   |  50 ++++++++-
 .../Entity/ConfigEntityBaseUnitTest.php       |   5 +-
 .../Config/Entity/ConfigEntityStorageTest.php |  16 +--
 67 files changed, 649 insertions(+), 221 deletions(-)
 mode change 100755 => 100644 core/modules/block/src/Entity/Block.php
 create mode 100644 core/modules/language/tests/src/Unit/Config/LanguageConfigOverrideTest.php
 create mode 100644 core/modules/system/src/EventSubscriber/ThemeSettingsCacheTag.php
 create mode 100644 core/tests/Drupal/Tests/Core/Config/ConfigFactoryTest.php

diff --git a/core/core.services.yml b/core/core.services.yml
index 9f35c9de203b..a9af34679cca 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -406,7 +406,7 @@ services:
     arguments: ['@plugin.manager.menu.link']
   menu.tree_storage:
     class: Drupal\Core\Menu\MenuTreeStorage
-    arguments: ['@database', '@cache.menu', 'menu_tree']
+    arguments: ['@database', '@cache.menu', '@cache_tags.invalidator', 'menu_tree']
     public: false  # Private to plugin.manager.menu.link and menu.link_tree
     tags:
       - { name: backend_overridable }
diff --git a/core/includes/module.inc b/core/includes/module.inc
index 97638fa43824..1ac014a1e2a5 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -69,7 +69,7 @@ function system_list_reset() {
   // @todo Trigger an event upon module install/uninstall and theme
   //   enable/disable, and move this into an event subscriber.
   // @see https://drupal.org/node/2206347
-  Cache::invalidateTags(array('extension'));
+  Cache::invalidateTags(['config:core.extension']);
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Access/AccessManager.php b/core/lib/Drupal/Core/Access/AccessManager.php
index 576ffb7015f0..a59f6970dc6a 100644
--- a/core/lib/Drupal/Core/Access/AccessManager.php
+++ b/core/lib/Drupal/Core/Access/AccessManager.php
@@ -97,7 +97,7 @@ public function checkNamedRoute($route_name, array $parameters = array(), Accoun
     }
     catch (RouteNotFoundException $e) {
       // Cacheable until extensions change.
-      $result = AccessResult::forbidden()->addCacheTags(array('extension'));
+      $result = AccessResult::forbidden()->addCacheTags(['config:core.extension']);
       return $return_as_object ? $result : $result->isAllowed();
     }
     catch (ParamNotConvertedException $e) {
diff --git a/core/lib/Drupal/Core/Access/AccessResult.php b/core/lib/Drupal/Core/Access/AccessResult.php
index 329d50d14fac..76f021264608 100644
--- a/core/lib/Drupal/Core/Access/AccessResult.php
+++ b/core/lib/Drupal/Core/Access/AccessResult.php
@@ -8,6 +8,7 @@
 
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheableInterface;
+use Drupal\Core\Config\ConfigBase;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Session\AccountInterface;
 
@@ -360,6 +361,19 @@ public function cacheUntilEntityChanges(EntityInterface $entity) {
     return $this;
   }
 
+  /**
+   * Convenience method, adds the configuration object's cache tag.
+   *
+   * @param \Drupal\Core\Config\ConfigBase $configuration
+   *   The configuration object whose cache tag to set on the access result.
+   *
+   * @return $this
+   */
+  public function cacheUntilConfigurationChanges(ConfigBase $configuration) {
+    $this->addCacheTags($configuration->getCacheTags());
+    return $this;
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/lib/Drupal/Core/Cache/Cache.php b/core/lib/Drupal/Core/Cache/Cache.php
index 4ce361007977..4e0124f41969 100644
--- a/core/lib/Drupal/Core/Cache/Cache.php
+++ b/core/lib/Drupal/Core/Cache/Cache.php
@@ -81,14 +81,16 @@ public static function validateTags(array $tags) {
    *   A prefix string.
    * @param array $suffixes
    *   An array of suffixes. Will be cast to strings.
+   * @param string $glue
+   *   A string to be used as glue for concatenation. Defaults to a colon.
    *
    * @return string[]
    *   An array of cache tags.
    */
-  public static function buildTags($prefix, array $suffixes) {
+  public static function buildTags($prefix, array $suffixes, $glue = ':') {
     $tags = [];
     foreach ($suffixes as $suffix) {
-      $tags[] = $prefix . ':' . $suffix;
+      $tags[] = $prefix . $glue . $suffix;
     }
     return $tags;
   }
diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php
index d3547998c86d..ed9c28efb376 100644
--- a/core/lib/Drupal/Core/Config/Config.php
+++ b/core/lib/Drupal/Core/Config/Config.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Config;
 
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Cache\Cache;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
@@ -223,6 +224,9 @@ public function save() {
     }
 
     $this->storage->write($this->name, $this->data);
+    if (!$this->isNew) {
+      Cache::invalidateTags($this->getCacheTags());
+    }
     $this->isNew = FALSE;
     $this->eventDispatcher->dispatch(ConfigEvents::SAVE, new ConfigCrudEvent($this));
     $this->originalData = $this->data;
@@ -238,6 +242,7 @@ public function save() {
   public function delete() {
     $this->data = array();
     $this->storage->delete($this->name);
+    Cache::invalidateTags($this->getCacheTags());
     $this->isNew = TRUE;
     $this->resetOverriddenData();
     $this->eventDispatcher->dispatch(ConfigEvents::DELETE, new ConfigCrudEvent($this));
diff --git a/core/lib/Drupal/Core/Config/ConfigBase.php b/core/lib/Drupal/Core/Config/ConfigBase.php
index f259dc767051..e94db9b9760a 100644
--- a/core/lib/Drupal/Core/Config/ConfigBase.php
+++ b/core/lib/Drupal/Core/Config/ConfigBase.php
@@ -262,4 +262,15 @@ public function merge(array $data_to_merge) {
     $this->setData(NestedArray::mergeDeepArray(array($this->data, $data_to_merge), TRUE));
     return $this;
   }
+
+  /**
+   * The unique cache tag associated with this configuration object.
+   *
+   * @return string[]
+   *   An array of cache tags.
+   */
+  public function getCacheTags() {
+    return ['config:' . $this->name];
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Config/ConfigFactory.php b/core/lib/Drupal/Core/Config/ConfigFactory.php
index b1a8932d9127..ce092d374ad3 100644
--- a/core/lib/Drupal/Core/Config/ConfigFactory.php
+++ b/core/lib/Drupal/Core/Config/ConfigFactory.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Config;
 
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Cache\Cache;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
@@ -218,6 +219,7 @@ public function reset($name = NULL) {
    * {@inheritdoc}
    */
   public function rename($old_name, $new_name) {
+    Cache::invalidateTags($this->get($old_name)->getCacheTags());
     $this->storage->rename($old_name, $new_name);
 
     // Clear out the static cache of any references to the old name.
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
index ad5f0d1c944a..b943d175da99 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Entity\Entity;
 use Drupal\Core\Config\ConfigDuplicateUUIDException;
 use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Plugin\PluginDependencyTrait;
@@ -371,6 +372,15 @@ public function link($text = NULL, $rel = 'edit-form', array $options = []) {
     return parent::link($text, $rel, $options);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags() {
+    // Use cache tags that match the underlying config object's name.
+    // @see \Drupal\Core\Config\ConfigBase::getCacheTags()
+    return ['config:' . $this->getConfigDependencyName()];
+  }
+
   /**
    * Overrides \Drupal\Core\Entity\DependencyTrait:addDependency().
    *
@@ -411,4 +421,24 @@ public function getConfigDependencyName() {
   public function onDependencyRemoval(array $dependencies) {
   }
 
+  /**
+   * {@inheritdoc}
+   *
+   * Override to never invalidate the entity's cache tag; the config system
+   * already invalidates it.
+   */
+  protected function invalidateTagsOnSave($update) {
+    Cache::invalidateTags($this->getEntityType()->getListCacheTags());
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Override to never invalidate the individual entities' cache tags; the
+   * config system already invalidates them.
+   */
+  protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_type, array $entities) {
+    Cache::invalidateTags($entity_type->getListCacheTags());
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
index 3fe34f1b0d95..23b0f4a5b7ea 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
@@ -64,6 +64,12 @@ class ConfigEntityType extends EntityType {
    * {@inheritdoc}
    */
   public function __construct($definition) {
+    // Ensure a default list cache tag is set; do this before calling the parent
+    // constructor, because we want "Configuration System style" cache tags.
+    if (empty($this->list_cache_tags)) {
+      $this->list_cache_tags = ['config:' . $definition['id'] . '_list'];
+    }
+
     parent::__construct($definition);
     // Always add a default 'uuid' key.
     $this->entity_keys['uuid'] = 'uuid';
diff --git a/core/lib/Drupal/Core/Config/StorableConfigBase.php b/core/lib/Drupal/Core/Config/StorableConfigBase.php
index 505a1bf28c82..4af470ec29f6 100644
--- a/core/lib/Drupal/Core/Config/StorableConfigBase.php
+++ b/core/lib/Drupal/Core/Config/StorableConfigBase.php
@@ -66,16 +66,18 @@ abstract class StorableConfigBase extends ConfigBase {
   /**
    * Saves the configuration object.
    *
-   * @return \Drupal\Core\Config\Config
-   *   The configuration object.
+   * Must invalidate the cache tags associated with the configuration object.
+   *
+   * @return $this
    */
   abstract public function save();
 
   /**
    * Deletes the configuration object.
    *
-   * @return \Drupal\Core\Config\Config
-   *   The configuration object.
+   * Must invalidate the cache tags associated with the configuration object.
+   *
+   * @return $this
    */
   abstract public function delete();
 
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index 24a0ebf23afd..08c35c7a41ec 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -407,7 +407,7 @@ public static function preDelete(EntityStorageInterface $storage, array $entitie
    * {@inheritdoc}
    */
   public static function postDelete(EntityStorageInterface $storage, array $entities) {
-    self::invalidateTagsOnDelete($storage->getEntityType(), $entities);
+    static::invalidateTagsOnDelete($storage->getEntityType(), $entities);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php
index fb1d932ee4ac..130612b85ef4 100644
--- a/core/lib/Drupal/Core/Entity/EntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityInterface.php
@@ -399,7 +399,7 @@ public function getTypedData();
   /**
    * The unique cache tag associated with this entity.
    *
-   * @return array
+   * @return string[]
    *   An array of cache tags.
    */
   public function getCacheTags();
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkTree.php b/core/lib/Drupal/Core/Menu/MenuLinkTree.php
index 61c60c2587e2..8851b2713d59 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkTree.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkTree.php
@@ -125,7 +125,7 @@ public function getCurrentRouteMenuTreeParameters($menu_name) {
           // expanded.
           ->addExpandedParents($this->treeStorage->getExpanded($menu_name, $active_trail));
 
-        $this->cache->set($cid, $parameters, CacheBackendInterface::CACHE_PERMANENT, array('menu:' . $menu_name));
+        $this->cache->set($cid, $parameters, CacheBackendInterface::CACHE_PERMANENT, array('config:system.menu.' . $menu_name));
       }
       $this->cachedCurrentRouteParameters[$menu_name] = $parameters;
     }
@@ -255,7 +255,7 @@ public function build(array $tree, $level = 0) {
       $build['#theme'] = 'menu__' . strtr($menu_name, '-', '_');
       $build['#items'] = $items;
       // Set cache tag.
-      $build['#cache']['tags'][] = 'menu:' . $menu_name;
+      $build['#cache']['tags'][] = 'config:system.menu.' . $menu_name;
       return $build;
     }
     else {
diff --git a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
index b8393b202778..4afd4fddbdb5 100644
--- a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
+++ b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
@@ -12,6 +12,7 @@
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\Query\SelectInterface;
@@ -41,6 +42,13 @@ class MenuTreeStorage implements MenuTreeStorageInterface {
    */
   protected $menuCacheBackend;
 
+  /**
+   * The cache tags invalidator.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
+   */
+  protected $cacheTagsInvalidator;
+
   /**
    * The database table name.
    *
@@ -109,14 +117,17 @@ class MenuTreeStorage implements MenuTreeStorageInterface {
    *   A Database connection to use for reading and writing configuration data.
    * @param \Drupal\Core\Cache\CacheBackendInterface $menu_cache_backend
    *   Cache backend instance for the extracted tree data.
+   * @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
+   *   The cache tags invalidator.
    * @param string $table
    *   A database table name to store configuration data in.
    * @param array $options
    *   (optional) Any additional database connection options to use in queries.
    */
-  public function __construct(Connection $connection, CacheBackendInterface $menu_cache_backend, $table, array $options = array()) {
+  public function __construct(Connection $connection, CacheBackendInterface $menu_cache_backend, CacheTagsInvalidatorInterface $cache_tags_invalidator, $table, array $options = array()) {
     $this->connection = $connection;
     $this->menuCacheBackend = $menu_cache_backend;
+    $this->cacheTagsInvalidator = $cache_tags_invalidator;
     $this->table = $table;
     $this->options = $options;
   }
@@ -180,8 +191,8 @@ public function rebuild(array $definitions) {
     $this->resetDefinitions();
     $affected_menus = $this->getMenuNames() + $before_menus;
     // Invalidate any cache tagged with any menu name.
-    $cache_tags = Cache::buildTags('menu', $affected_menus);
-    Cache::invalidateTags($cache_tags);
+    $cache_tags = Cache::buildTags('config:system.menu', $affected_menus, '.');
+    $this->cacheTagsInvalidator->invalidateTags($cache_tags);
     $this->resetDefinitions();
     // Every item in the cache bin should have one of the menu cache tags but it
     // is not guaranteed, so invalidate everything in the bin.
@@ -241,8 +252,8 @@ protected function safeExecuteSelect(SelectInterface $query) {
   public function save(array $link) {
     $affected_menus = $this->doSave($link);
     $this->resetDefinitions();
-    $cache_tags = Cache::buildTags('menu', $affected_menus);
-    Cache::invalidateTags($cache_tags);
+    $cache_tags = Cache::buildTags('config:system.menu', $affected_menus, '.');
+    $this->cacheTagsInvalidator->invalidateTags($cache_tags);
     return $affected_menus;
   }
 
@@ -394,7 +405,7 @@ public function delete($id) {
       $this->updateParentalStatus($item);
       // Many children may have moved.
       $this->resetDefinitions();
-      Cache::invalidateTags(array('menu:' . $item['menu_name']));
+      $this->cacheTagsInvalidator->invalidateTags(['config:system.menu.' . $item['menu_name']]);
     }
   }
 
@@ -823,7 +834,7 @@ public function loadTreeData($menu_name, MenuTreeParameters $parameters) {
       $data['tree'] = $this->doBuildTreeData($links, $parameters->activeTrail, $parameters->minDepth);
       $data['definitions'] = array();
       $data['route_names'] = $this->collectRoutesAndDefinitions($data['tree'], $data['definitions']);
-      $this->menuCacheBackend->set($tree_cid, $data, Cache::PERMANENT, array('menu:' . $menu_name));
+      $this->menuCacheBackend->set($tree_cid, $data, Cache::PERMANENT, ['config:system.menu.' . $menu_name]);
       // The definitions were already added to $this->definitions in
       // $this->doBuildTreeData()
       unset($data['definitions']);
diff --git a/core/lib/Drupal/Core/Menu/StaticMenuLinkOverrides.php b/core/lib/Drupal/Core/Menu/StaticMenuLinkOverrides.php
index 86d8f85b683d..789e25845deb 100644
--- a/core/lib/Drupal/Core/Menu/StaticMenuLinkOverrides.php
+++ b/core/lib/Drupal/Core/Menu/StaticMenuLinkOverrides.php
@@ -143,6 +143,13 @@ public function saveOverride($id, array $definition) {
     return array_keys($definition);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags() {
+    return $this->getConfig()->getCacheTags();
+  }
+
   /**
    * Encodes the ID by replacing dots with double underscores.
    *
diff --git a/core/lib/Drupal/Core/Menu/StaticMenuLinkOverridesInterface.php b/core/lib/Drupal/Core/Menu/StaticMenuLinkOverridesInterface.php
index 43c4416f12f9..f3f429ab1809 100644
--- a/core/lib/Drupal/Core/Menu/StaticMenuLinkOverridesInterface.php
+++ b/core/lib/Drupal/Core/Menu/StaticMenuLinkOverridesInterface.php
@@ -84,4 +84,12 @@ public function loadMultipleOverrides(array $ids);
    */
   public function saveOverride($id, array $definition);
 
+  /**
+   * The unique cache tag associated with this menu link override.
+   *
+   * @return string[]
+   *   An array of cache tags.
+   */
+  public function getCacheTags();
+
 }
diff --git a/core/lib/Drupal/Core/Render/Element/Page.php b/core/lib/Drupal/Core/Render/Element/Page.php
index 0d8b777e3278..b65446a47294 100644
--- a/core/lib/Drupal/Core/Render/Element/Page.php
+++ b/core/lib/Drupal/Core/Render/Element/Page.php
@@ -20,29 +20,11 @@ class Page extends RenderElement {
    * {@inheritdoc}
    */
   public function getInfo() {
-    $class = get_class($this);
     return array(
       '#show_messages' => TRUE,
-      '#pre_render' => array(
-        array($class, 'preRenderPage'),
-      ),
       '#theme' => 'page',
       '#title' => '',
     );
   }
 
-  /**
-   * #pre_render callback for the page element type.
-   *
-   * @param array $element
-   *   A structured array containing the page element type build properties.
-   *
-   * @return array
-   */
-  public static function preRenderPage($element) {
-    $element['#cache']['tags'][] = 'theme:' . \Drupal::theme()->getActiveTheme()->getName();
-    $element['#cache']['tags'][] = 'theme_global_settings';
-    return $element;
-  }
-
 }
diff --git a/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php b/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php
index 1e61f15584e7..fdd6c776ccb2 100644
--- a/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php
+++ b/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php
@@ -42,8 +42,8 @@ public function __construct(ThemeHandlerInterface $theme_handler) {
    *   The access result.
    */
   public function access($theme) {
-    // Cacheable until the theme is modified.
-    return AccessResult::allowedIf($this->checkAccess($theme))->addCacheTags(array('theme:' . $theme));
+    // Cacheable until the theme settings are modified.
+    return AccessResult::allowedIf($this->checkAccess($theme))->addCacheTags(['config:' . $theme . '.settings']);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Theme/ThemeSettings.php b/core/lib/Drupal/Core/Theme/ThemeSettings.php
index f1ad829fd047..ab0d08186d55 100644
--- a/core/lib/Drupal/Core/Theme/ThemeSettings.php
+++ b/core/lib/Drupal/Core/Theme/ThemeSettings.php
@@ -46,4 +46,12 @@ public function __construct($theme) {
   public function getTheme() {
     return $this->theme;
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags() {
+    return ['rendered'];
+  }
+
 }
diff --git a/core/modules/block/src/Entity/Block.php b/core/modules/block/src/Entity/Block.php
old mode 100755
new mode 100644
index 283c718803b1..4af2f80be2d8
--- a/core/modules/block/src/Entity/Block.php
+++ b/core/modules/block/src/Entity/Block.php
@@ -237,19 +237,6 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) {
     }
   }
 
-  /**
-   * {@inheritdoc}
-   *
-   * Block configuration entities are a special case: one block entity stores
-   * the placement of one block in one theme. Changing these entities may affect
-   * any page that is rendered in a certain theme, even if the block doesn't
-   * appear there currently. Hence a block configuration entity must also return
-   * the associated theme's cache tag.
-   */
-  public function getCacheTags() {
-    return Cache::mergeTags(parent::getCacheTags(), ['theme:' . $this->theme]);
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php b/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php
index 895e2c627220..bf91a04fd4e9 100644
--- a/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php
+++ b/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php
@@ -12,6 +12,7 @@
 use Drupal\block\Event\BlockEvents;
 use Drupal\Core\Block\MainContentBlockPluginInterface;
 use Drupal\Core\Display\PageVariantInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityViewBuilderInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Display\VariantBase;
@@ -43,11 +44,11 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont
   protected $blockViewBuilder;
 
   /**
-   * The current theme.
+   * The Block entity type list cache tags.
    *
-   * @var string
+   * @var string[]
    */
-  protected $theme;
+  protected $blockListCacheTags;
 
   /**
    * The render array representing the main page content.
@@ -71,12 +72,15 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont
    *   The block view builder.
    * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
    *   The event dispatcher.
+   * @param string[] $block_list_cache_tags
+   *   The Block entity type list cache tags.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, EventDispatcherInterface $dispatcher) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, EventDispatcherInterface $dispatcher, array $block_list_cache_tags) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->blockRepository = $block_repository;
     $this->blockViewBuilder = $block_view_builder;
     $this->dispatcher = $dispatcher;
+    $this->blockListCacheTags = $block_list_cache_tags;
   }
 
   /**
@@ -89,7 +93,8 @@ public static function create(ContainerInterface $container, array $configuratio
       $plugin_definition,
       $container->get('block.repository'),
       $container->get('entity.manager')->getViewBuilder('block'),
-      $container->get('event_dispatcher')
+      $container->get('event_dispatcher'),
+      $container->get('entity.manager')->getDefinition('block')->getListCacheTags()
     );
   }
 
@@ -108,7 +113,11 @@ public function build() {
     // Track whether a block that shows the main content is displayed or not.
     $main_content_block_displayed = FALSE;
 
-    $build = array();
+    $build = [
+      '#cache' => [
+        'tags' => $this->blockListCacheTags,
+      ],
+    ];
     $contexts = $this->getActiveBlockContexts();
     // Load all region content assigned via blocks.
     foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts) as $region => $blocks) {
diff --git a/core/modules/block/src/Tests/BlockTest.php b/core/modules/block/src/Tests/BlockTest.php
index 5c8d900dfd40..1092ea59dd1a 100644
--- a/core/modules/block/src/Tests/BlockTest.php
+++ b/core/modules/block/src/Tests/BlockTest.php
@@ -327,10 +327,9 @@ public function testBlockCacheTags() {
     $cid = implode(':', $cid_parts);
     $cache_entry = \Drupal::cache('render')->get($cid);
     $expected_cache_tags = array(
-      'theme:classy',
-      'theme_global_settings',
+      'config:block_list',
       'block_view',
-      'block:powered',
+      'config:block.block.powered',
       'block_plugin:system_powered_by_block',
       'rendered',
     );
@@ -339,8 +338,7 @@ public function testBlockCacheTags() {
     $cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:en:classy');
     $expected_cache_tags = array(
       'block_view',
-      'block:powered',
-      'theme:classy',
+      'config:block.block.powered',
       'block_plugin:system_powered_by_block',
       'rendered',
     );
@@ -369,11 +367,10 @@ public function testBlockCacheTags() {
     $cid = implode(':', $cid_parts);
     $cache_entry = \Drupal::cache('render')->get($cid);
     $expected_cache_tags = array(
-      'theme:classy',
-      'theme_global_settings',
+      'config:block_list',
       'block_view',
-      'block:powered-2',
-      'block:powered',
+      'config:block.block.powered',
+      'config:block.block.powered-2',
       'block_plugin:system_powered_by_block',
       'rendered',
     );
@@ -381,8 +378,7 @@ public function testBlockCacheTags() {
     $this->assertEqual($cache_entry->tags, $expected_cache_tags);
     $expected_cache_tags = array(
       'block_view',
-      'block:powered',
-      'theme:classy',
+      'config:block.block.powered',
       'block_plugin:system_powered_by_block',
       'rendered',
     );
@@ -391,8 +387,7 @@ public function testBlockCacheTags() {
     $this->assertIdentical($cache_entry->tags, $expected_cache_tags);
     $expected_cache_tags = array(
       'block_view',
-      'block:powered-2',
-      'theme:classy',
+      'config:block.block.powered-2',
       'block_plugin:system_powered_by_block',
       'rendered',
     );
diff --git a/core/modules/block/src/Tests/BlockViewBuilderTest.php b/core/modules/block/src/Tests/BlockViewBuilderTest.php
index 2ddbcab9b04e..bfd37d4a7858 100644
--- a/core/modules/block/src/Tests/BlockViewBuilderTest.php
+++ b/core/modules/block/src/Tests/BlockViewBuilderTest.php
@@ -215,7 +215,7 @@ public function testBlockViewBuilderAlter() {
     $request->setMethod('GET');
 
     $default_keys = array('entity_view', 'block', 'test_block', 'en', 'cache_context.theme');
-    $default_tags = array('block_view', 'block:test_block', 'theme:stark', 'block_plugin:test_cache');
+    $default_tags = array('block_view', 'config:block.block.test_block', 'block_plugin:test_cache');
 
     // Advanced: cached block, but an alter hook adds an additional cache key.
     $this->setBlockCacheConfig(array(
@@ -230,7 +230,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_tags = array('block_view', 'block:test_block', 'theme:stark', 'block_plugin:test_cache', 'rendered');
+    $expected_tags = array_merge($default_tags, ['rendered']);
     sort($expected_tags);
     $this->assertIdentical($cache_entry->tags, $expected_tags, 'The block render element has been cached with the expected cache tags.');
     $this->container->get('cache.render')->delete($cid);
@@ -246,7 +246,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_tags = array('block_view', 'block:test_block', 'theme:stark', 'block_plugin:test_cache', $alter_add_tag, 'rendered');
+    $expected_tags = array_merge($default_tags, [$alter_add_tag, 'rendered']);
     sort($expected_tags);
     $this->assertIdentical($cache_entry->tags, $expected_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/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php b/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php
index 0ae38d4f7df8..c39d744a8c07 100644
--- a/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php
+++ b/core/modules/block/tests/src/Unit/Plugin/DisplayVariant/BlockPageVariantTest.php
@@ -61,9 +61,8 @@ public function setUpDisplayVariant($configuration = array(), $definition = arra
     $this->dispatcher->expects($this->any())
       ->method('dispatch')
       ->willReturnArgument(1);
-    $this->contextHandler = $this->getMock('Drupal\Core\Plugin\Context\ContextHandlerInterface');
     return $this->getMockBuilder('Drupal\block\Plugin\DisplayVariant\BlockPageVariant')
-      ->setConstructorArgs(array($configuration, 'test', $definition, $this->blockRepository, $this->blockViewBuilder, $this->dispatcher, $this->contextHandler))
+      ->setConstructorArgs(array($configuration, 'test', $definition, $this->blockRepository, $this->blockViewBuilder, $this->dispatcher, ['config:block_list']))
       ->setMethods(array('getRegionNames'))
       ->getMock();
   }
@@ -89,6 +88,11 @@ public function providerBuild() {
     $test_cases = [];
     $test_cases[] = [$blocks_config, 4,
       [
+        '#cache' => [
+          'tags' => [
+            'config:block_list',
+          ],
+        ],
         'top' => [
           'block1' => [],
           '#sorted' => TRUE,
@@ -108,6 +112,11 @@ public function providerBuild() {
     unset($blocks_config['block4']);
     $test_cases[] = [$blocks_config, 3,
       [
+        '#cache' => [
+          'tags' => [
+            'config:block_list',
+          ],
+        ],
         'top' => [
           'block1' => [],
           '#sorted' => TRUE,
@@ -170,7 +179,16 @@ public function testBuildWithoutMainContent() {
       ->method('getVisibleBlocksPerRegion')
       ->willReturn([]);
 
-    $expected = ['content' => ['system_main' => []]];
+    $expected = [
+      '#cache' => [
+        'tags' => [
+          'config:block_list',
+        ],
+      ],
+      'content' => [
+        'system_main' => [],
+      ],
+    ];
     $this->assertSame($expected, $display_variant->build());
   }
 
diff --git a/core/modules/block_content/src/Tests/BlockContentCacheTagsTest.php b/core/modules/block_content/src/Tests/BlockContentCacheTagsTest.php
index 1ea2156da2b8..b02086d0f34a 100644
--- a/core/modules/block_content/src/Tests/BlockContentCacheTagsTest.php
+++ b/core/modules/block_content/src/Tests/BlockContentCacheTagsTest.php
@@ -55,7 +55,7 @@ protected function createEntity() {
    * Each comment must have a comment body, which always has a text format.
    */
   protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
-    return array('filter_format:plain_text');
+    return ['config:filter.format.plain_text'];
   }
 
 }
diff --git a/core/modules/comment/src/Tests/CommentCacheTagsTest.php b/core/modules/comment/src/Tests/CommentCacheTagsTest.php
index 5da555518921..6f83dbfc6bf9 100644
--- a/core/modules/comment/src/Tests/CommentCacheTagsTest.php
+++ b/core/modules/comment/src/Tests/CommentCacheTagsTest.php
@@ -86,7 +86,7 @@ protected function createEntity() {
   protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
     /** @var \Drupal\comment\CommentInterface $entity */
     return array(
-      'filter_format:plain_text',
+      'config:filter.format.plain_text',
       'user:' . $entity->getOwnerId(),
       'user_view',
     );
diff --git a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php
index 5e4359dbb3a8..1a97e1b69fe4 100644
--- a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php
+++ b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php
@@ -107,7 +107,7 @@ public function testCacheTags() {
       'comment_list',
       'comment_view',
       'comment:' . $comment->id(),
-      'filter_format:plain_text',
+      'config:filter.format.plain_text',
       'user_view',
       'user:2',
     );
diff --git a/core/modules/contact/src/Access/ContactPageAccess.php b/core/modules/contact/src/Access/ContactPageAccess.php
index e1a52945cb59..97d6b06f6f7b 100644
--- a/core/modules/contact/src/Access/ContactPageAccess.php
+++ b/core/modules/contact/src/Access/ContactPageAccess.php
@@ -93,7 +93,9 @@ public function access(UserInterface $user, AccountInterface $account) {
     }
     // If the requested user did not save a preference yet, deny access if the
     // configured default is disabled.
-    else if (!$this->configFactory->get('contact.settings')->get('user_default_enabled')) {
+    $contact_settings = $this->configFactory->get('contact.settings');
+    $access->cacheUntilConfigurationChanges($contact_settings);
+    if (!isset($account_data) && !$contact_settings->get('user_default_enabled')) {
       return $access;
     }
 
diff --git a/core/modules/filter/src/Tests/FilterAPITest.php b/core/modules/filter/src/Tests/FilterAPITest.php
index d4c47bf7e8cc..50210b591aa8 100644
--- a/core/modules/filter/src/Tests/FilterAPITest.php
+++ b/core/modules/filter/src/Tests/FilterAPITest.php
@@ -247,7 +247,7 @@ function testProcessedTextElement() {
     $this->assertEqual($expected_assets, $build['#attached'], 'Expected assets present');
     $expected_cache_tags = array(
       // The cache tag set by the processed_text element itself.
-      'filter_format:element_test',
+      'config:filter.format.element_test',
       // The cache tags set by the filter_test_cache_tags filter.
       'foo:bar',
       'foo:baz',
diff --git a/core/modules/image/src/Tests/ImageFieldDisplayTest.php b/core/modules/image/src/Tests/ImageFieldDisplayTest.php
index f006abd5c76c..fc6854b6f2ef 100644
--- a/core/modules/image/src/Tests/ImageFieldDisplayTest.php
+++ b/core/modules/image/src/Tests/ImageFieldDisplayTest.php
@@ -173,7 +173,7 @@ function _testImageFieldFormatters($scheme) {
     $default_output = drupal_render($image_style);
     $this->drupalGet('node/' . $nid);
     $cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
-    $this->assertTrue(in_array('image_style:thumbnail', $cache_tags));
+    $this->assertTrue(in_array('config:image.style.thumbnail', $cache_tags));
     $this->assertRaw($default_output, 'Image style thumbnail formatter displaying correctly on full node view.');
 
     if ($scheme == 'private') {
diff --git a/core/modules/language/src/Config/LanguageConfigOverride.php b/core/modules/language/src/Config/LanguageConfigOverride.php
index d64dda757f77..5be48ad5d22c 100644
--- a/core/modules/language/src/Config/LanguageConfigOverride.php
+++ b/core/modules/language/src/Config/LanguageConfigOverride.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\language\Config;
 
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Config\StorableConfigBase;
 use Drupal\Core\Config\StorageInterface;
 use Drupal\Core\Config\TypedConfigManagerInterface;
@@ -57,6 +58,11 @@ public function save() {
       $this->validateValue($key, $value);
     }
     $this->storage->write($this->name, $this->data);
+    // Invalidate the cache tags not only when updating, but also when creating,
+    // because a language config override object uses the same cache tag as the
+    // default configuration object. Hence creating a language override is like
+    // an update of configuration, but only for a specific language.
+    Cache::invalidateTags($this->getCacheTags());
     $this->isNew = FALSE;
     $this->eventDispatcher->dispatch(LanguageConfigOverrideEvents::SAVE_OVERRIDE, new LanguageConfigOverrideCrudEvent($this));
     $this->originalData = $this->data;
@@ -69,6 +75,7 @@ public function save() {
   public function delete() {
     $this->data = array();
     $this->storage->delete($this->name);
+    Cache::invalidateTags($this->getCacheTags());
     $this->isNew = TRUE;
     $this->eventDispatcher->dispatch(LanguageConfigOverrideEvents::DELETE_OVERRIDE, new LanguageConfigOverrideCrudEvent($this));
     $this->originalData = $this->data;
diff --git a/core/modules/language/tests/src/Unit/Config/LanguageConfigOverrideTest.php b/core/modules/language/tests/src/Unit/Config/LanguageConfigOverrideTest.php
new file mode 100644
index 000000000000..0c979d9d7d40
--- /dev/null
+++ b/core/modules/language/tests/src/Unit/Config/LanguageConfigOverrideTest.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\language\Unit\Config\LanguageConfigOverrideTest.
+ */
+
+namespace Drupal\Tests\language\Unit\Config;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\language\Config\LanguageConfigOverride;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\language\Config\LanguageConfigOverride
+ * @group Config
+ * @group language
+ */
+class LanguageConfigOverrideTest extends UnitTestCase {
+
+  /**
+   * Language configuration override.
+   *
+   * @var \Drupal\language\Config\LanguageConfigOverride
+   */
+  protected $configTranslation;
+
+  /**
+   * Storage.
+   *
+   * @var \Drupal\Core\Config\StorageInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $storage;
+
+  /**
+   * Event Dispatcher.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $eventDispatcher;
+
+  /**
+   * Typed Config.
+   *
+   * @var \Drupal\Core\Config\TypedConfigManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $typedConfig;
+
+  /**
+   * The mocked cache tags invalidator.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cacheTagsInvalidator;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    $this->storage = $this->getMock('Drupal\Core\Config\StorageInterface');
+    $this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
+    $this->typedConfig = $this->getMock('\Drupal\Core\Config\TypedConfigManagerInterface');
+    $this->configTranslation = new LanguageConfigOverride('config.test', $this->storage, $this->typedConfig, $this->eventDispatcher);
+    $this->cacheTagsInvalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
+
+    $container = new ContainerBuilder();
+    $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator);
+    \Drupal::setContainer($container);
+  }
+
+  /**
+   * @covers ::save
+   */
+  public function testSaveNew() {
+    $this->cacheTagsInvalidator->expects($this->once())
+      ->method('invalidateTags')
+      ->with(['config:config.test']);
+    $this->assertTrue($this->configTranslation->isNew());
+    $this->configTranslation->save();
+  }
+
+  /**
+   * @covers ::save
+   */
+  public function testSaveExisting() {
+    $this->cacheTagsInvalidator->expects($this->once())
+      ->method('invalidateTags')
+      ->with(['config:config.test']);
+    $this->configTranslation->initWithData([]);
+    $this->configTranslation->save();
+  }
+
+  /**
+   * @covers ::delete
+   */
+  public function testDelete() {
+    $this->cacheTagsInvalidator->expects($this->once())
+      ->method('invalidateTags')
+      ->with(['config:config.test']);
+    $this->configTranslation->initWithData([]);
+    $this->configTranslation->delete();
+  }
+
+}
diff --git a/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php b/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php
index ba196515c070..96432d4838cf 100644
--- a/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php
+++ b/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php
@@ -48,13 +48,12 @@ public function testMenuBlock() {
 
     // Verify a cache hit, but also the presence of the correct cache tags.
     $expected_tags = array(
-      'theme:classy',
-      'theme_global_settings',
       'rendered',
       'block_view',
-      'block:' . $block->id(),
+      'config:block_list',
+      'config:block.block.' . $block->id(),
       'block_plugin:system_menu_block__llama',
-      'menu:llama',
+      'config:system.menu.llama',
     );
     $this->verifyPageCache($path, 'HIT', $expected_tags);
 
@@ -106,7 +105,7 @@ public function testMenuBlock() {
     $this->verifyPageCache($path, 'MISS');
 
     // Verify a cache hit.
-    $this->verifyPageCache($path, 'HIT', array('rendered', 'theme:classy', 'theme_global_settings'));
+    $this->verifyPageCache($path, 'HIT', ['config:block_list', 'rendered']);
   }
 
 }
diff --git a/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php b/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php
index 41e4f1c8f7d8..09efc576b1e7 100644
--- a/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php
+++ b/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php
@@ -202,12 +202,12 @@ protected function doTestResponsiveImageFieldFormatters($scheme, $empty_styles =
     $this->assertRaw('media="(min-width: 560px)"');
     $this->assertRaw('media="(min-width: 851px)"');
     $cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
-    $this->assertTrue(in_array('responsive_image_mapping:mapping_one', $cache_tags));
+    $this->assertTrue(in_array('config:responsive_image.mappings.mapping_one', $cache_tags));
     if (!$empty_styles) {
-      $this->assertTrue(in_array('image_style:thumbnail', $cache_tags));
-      $this->assertTrue(in_array('image_style:medium', $cache_tags));
+      $this->assertTrue(in_array('config:image.style.thumbnail', $cache_tags));
+      $this->assertTrue(in_array('config:image.style.medium', $cache_tags));
     }
-    $this->assertTrue(in_array('image_style:large', $cache_tags));
+    $this->assertTrue(in_array('config:image.style.large', $cache_tags));
 
     // Test the fallback image style.
     $large_style = entity_load('image_style', 'large');
diff --git a/core/modules/search/src/Tests/SearchPageCacheTagsTest.php b/core/modules/search/src/Tests/SearchPageCacheTagsTest.php
index da975f6ddb72..e52c2143fa4d 100644
--- a/core/modules/search/src/Tests/SearchPageCacheTagsTest.php
+++ b/core/modules/search/src/Tests/SearchPageCacheTagsTest.php
@@ -41,25 +41,25 @@ function testSearchText() {
     // Initial page for searching nodes.
     $this->drupalGet('search/node');
     $cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
-    $this->assertTrue(in_array('search_page:node_search', $cache_tags));
+    $this->assertTrue(in_array('config:search.page.node_search', $cache_tags));
 
     // Node search results.
     $edit = array();
     $edit['keys'] = 'bike shed';
     $this->drupalPostForm('search/node', $edit, t('Search'));
     $cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
-    $this->assertTrue(in_array('search_page:node_search', $cache_tags));
+    $this->assertTrue(in_array('config:search.page.node_search', $cache_tags));
 
     // Initial page for searching users.
     $this->drupalGet('search/user');
     $cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
-    $this->assertTrue(in_array('search_page:user_search', $cache_tags));
+    $this->assertTrue(in_array('config:search.page.user_search', $cache_tags));
 
     // User search results.
     $edit['keys'] = $this->searchingUser->getUsername();
     $this->drupalPostForm('search/user', $edit, t('Search'));
     $cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
-    $this->assertTrue(in_array('search_page:user_search', $cache_tags));
+    $this->assertTrue(in_array('config:search.page.user_search', $cache_tags));
   }
 
 }
diff --git a/core/modules/shortcut/src/Entity/Shortcut.php b/core/modules/shortcut/src/Entity/Shortcut.php
index da408811a066..91a7f0bc7241 100644
--- a/core/modules/shortcut/src/Entity/Shortcut.php
+++ b/core/modules/shortcut/src/Entity/Shortcut.php
@@ -47,7 +47,7 @@
  *     "delete-form" = "/admin/config/user-interface/shortcut/link/{shortcut}/delete",
  *     "edit-form" = "/admin/config/user-interface/shortcut/link/{shortcut}",
  *   },
- *   list_cache_tags = { "shortcut_set_list" },
+ *   list_cache_tags = { "config:shortcut_set_list" },
  *   bundle_entity_type = "shortcut_set"
  * )
  */
diff --git a/core/modules/shortcut/src/Tests/ShortcutCacheTagsTest.php b/core/modules/shortcut/src/Tests/ShortcutCacheTagsTest.php
index 98b0904b45f8..2fd7385b4a30 100644
--- a/core/modules/shortcut/src/Tests/ShortcutCacheTagsTest.php
+++ b/core/modules/shortcut/src/Tests/ShortcutCacheTagsTest.php
@@ -58,11 +58,11 @@ protected function createEntity() {
    */
   public function testEntityCreation() {
     // Create a cache entry that is tagged with a shortcut set cache tag.
-    $cache_tags = array('shortcut_set:default');
+    $cache_tags = ['config:shortcut.set.default'];
     \Drupal::cache('render')->set('foo', 'bar', \Drupal\Core\Cache\CacheBackendInterface::CACHE_PERMANENT, $cache_tags);
 
     // Verify a cache hit.
-    $this->verifyRenderCache('foo', array('shortcut_set:default'));
+    $this->verifyRenderCache('foo', $cache_tags);
 
     // Now create a shortcut entity in that shortcut set.
     $this->createEntity();
diff --git a/core/modules/system/src/EventSubscriber/ThemeSettingsCacheTag.php b/core/modules/system/src/EventSubscriber/ThemeSettingsCacheTag.php
new file mode 100644
index 000000000000..eb68d7523b40
--- /dev/null
+++ b/core/modules/system/src/EventSubscriber/ThemeSettingsCacheTag.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\EventSubscriber\ThemeSettingsCacheTag.
+ */
+
+namespace Drupal\system\EventSubscriber;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
+use Drupal\Core\Config\ConfigCrudEvent;
+use Drupal\Core\Config\ConfigEvents;
+use Drupal\Core\Extension\ThemeHandlerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * A subscriber invalidating the 'rendered' cache tag when saving theme settings.
+ */
+class ThemeSettingsCacheTag implements EventSubscriberInterface {
+
+  /**
+   * The theme handler.
+   *
+   * @var \Drupal\Core\Extension\ThemeHandlerInterface
+   */
+  protected $themeHandler;
+
+  /**
+   * The cache tags invalidator.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
+   */
+  protected $cacheTagsInvalidator;
+
+  /**
+   * Constructs a ThemeSettingsCacheTag object.
+   *
+   * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
+   *   The theme handler.
+   * @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
+   *   The cache tags invalidator.
+   */
+  public function __construct(ThemeHandlerInterface $theme_handler, CacheTagsInvalidatorInterface $cache_tags_invalidator) {
+    $this->themeHandler = $theme_handler;
+    $this->cacheTagsInvalidator = $cache_tags_invalidator;
+  }
+
+  /**
+   * Invalidate the 'rendered' cache tag whenever a theme setting is modified.
+   *
+   * @param \Drupal\Core\Config\ConfigCrudEvent $event
+   *   The Event to process.
+   */
+  public function onSave(ConfigCrudEvent $event) {
+    // Global theme settings.
+    if ($event->getConfig()->getName() === 'system.theme.global') {
+      $this->cacheTagsInvalidator->invalidateTags(['rendered']);
+    }
+
+    // Theme-specific settings, check if this matches a theme settings
+    // configuration object, in that case, clear the rendered cache tag.
+    foreach (array_keys($this->themeHandler->listInfo()) as $theme_name) {
+      if ($theme_name == $event->getConfig()->getName()) {
+        $this->cacheTagsInvalidator->invalidateTags(['rendered']);
+        break;
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[ConfigEvents::SAVE][] = ['onSave'];
+    return $events;
+  }
+
+}
diff --git a/core/modules/system/src/Form/ThemeSettingsForm.php b/core/modules/system/src/Form/ThemeSettingsForm.php
index 36bf64a81b81..959fd565be13 100644
--- a/core/modules/system/src/Form/ThemeSettingsForm.php
+++ b/core/modules/system/src/Form/ThemeSettingsForm.php
@@ -431,15 +431,6 @@ public function submitForm(array &$form, FormStateInterface $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'));
-    }
   }
 
   /**
diff --git a/core/modules/system/src/Plugin/Block/SystemMenuBlock.php b/core/modules/system/src/Plugin/Block/SystemMenuBlock.php
index 3d968550aaf4..819ff5502ff5 100644
--- a/core/modules/system/src/Plugin/Block/SystemMenuBlock.php
+++ b/core/modules/system/src/Plugin/Block/SystemMenuBlock.php
@@ -198,7 +198,7 @@ public function getCacheTags() {
     // menu block must also be re-rendered for that user, because maybe a menu
     // link that is accessible for that user has been added.
     $cache_tags = parent::getCacheTags();
-    $cache_tags[] = 'menu:' . $this->getDerivativeId();
+    $cache_tags[] = 'config:system.menu.' . $this->getDerivativeId();
     return $cache_tags;
   }
 
diff --git a/core/modules/system/src/Tests/Bootstrap/PageCacheTest.php b/core/modules/system/src/Tests/Bootstrap/PageCacheTest.php
index a016161d4d0b..c3538f2cc8bf 100644
--- a/core/modules/system/src/Tests/Bootstrap/PageCacheTest.php
+++ b/core/modules/system/src/Tests/Bootstrap/PageCacheTest.php
@@ -65,8 +65,6 @@ function testPageCacheTags() {
       'pre_render',
       'rendered',
       'system_test_cache_tags_page',
-      'theme:classy',
-      'theme_global_settings',
     );
     $this->assertIdentical($cache_entry->tags, $expected_tags);
 
diff --git a/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php b/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
index 1991a31bbee6..3fc3e3413ab8 100644
--- a/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
+++ b/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
@@ -69,16 +69,15 @@ function testPageCacheTags() {
     // Full node page 1.
     $this->verifyPageCacheTags('node/' . $node_1->id(), array(
       'rendered',
-      'theme:bartik',
-      'theme_global_settings',
       'block_view',
-      'block:bartik_content',
-      'block:bartik_tools',
-      'block:bartik_login',
-      'block:bartik_footer',
-      'block:bartik_powered',
-      'block:bartik_main_menu',
-      'block:bartik_account_menu',
+      'config:block_list',
+      'config:block.block.bartik_content',
+      'config:block.block.bartik_tools',
+      'config:block.block.bartik_login',
+      'config:block.block.bartik_footer',
+      'config:block.block.bartik_powered',
+      'config:block.block.bartik_main_menu',
+      'config:block.block.bartik_account_menu',
       'block_plugin:system_main_block',
       'block_plugin:system_menu_block__account',
       'block_plugin:system_menu_block__main',
@@ -89,27 +88,26 @@ function testPageCacheTags() {
       'node_view',
       'node:' . $node_1->id(),
       'user:' . $author_1->id(),
-      'filter_format:basic_html',
-      'menu:account',
-      'menu:tools',
-      'menu:footer',
-      'menu:main',
+      'config:filter.format.basic_html',
+      'config:system.menu.account',
+      'config:system.menu.tools',
+      'config:system.menu.footer',
+      'config:system.menu.main',
     ));
 
     // Full node page 2.
     $this->verifyPageCacheTags('node/' . $node_2->id(), array(
       'rendered',
-      'theme:bartik',
-      'theme_global_settings',
       'block_view',
-      'block:bartik_content',
-      'block:bartik_tools',
-      'block:bartik_login',
-      'block:' . $block->id(),
-      'block:bartik_footer',
-      'block:bartik_powered',
-      'block:bartik_main_menu',
-      'block:bartik_account_menu',
+      'config:block_list',
+      'config:block.block.bartik_content',
+      'config:block.block.bartik_tools',
+      'config:block.block.bartik_login',
+      'config:block.block.' . $block->id(),
+      'config:block.block.bartik_footer',
+      'config:block.block.bartik_powered',
+      'config:block.block.bartik_main_menu',
+      'config:block.block.bartik_account_menu',
       'block_plugin:system_main_block',
       'block_plugin:system_menu_block__account',
       'block_plugin:system_menu_block__main',
@@ -121,11 +119,11 @@ function testPageCacheTags() {
       'node_view',
       'node:' . $node_2->id(),
       'user:' . $author_2->id(),
-      'filter_format:full_html',
-      'menu:account',
-      'menu:tools',
-      'menu:footer',
-      'menu:main',
+      'config:filter.format.full_html',
+      'config:system.menu.account',
+      'config:system.menu.tools',
+      'config:system.menu.footer',
+      'config:system.menu.main',
     ));
   }
 
diff --git a/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php b/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php
index 07d464e90f16..658136c45d67 100644
--- a/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php
+++ b/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php
@@ -283,8 +283,13 @@ public function testReferencedEntity() {
     $empty_entity_listing_path = 'entity_test/list_empty/' . $entity_type;
     $nonempty_entity_listing_path = 'entity_test/list_labels_alphabetically/' . $entity_type;
 
-    $render_cache_tags = array('rendered');
-    $theme_cache_tags = array('theme:classy', 'theme_global_settings');
+    // Cache tags present on every rendered page.
+    $page_cache_tags = Cache::mergeTags(
+      ['rendered'],
+      // If the block module is used, the Block page display variant is used,
+      // which adds the block config entity type's list cache tags.
+      \Drupal::moduleHandler()->moduleExists('block') ? ['config:block_list']: []
+    );
 
     $view_cache_tag = array();
     if ($this->entity->getEntityType()->hasHandlerClass('view_builder')) {
@@ -299,11 +304,13 @@ public function testReferencedEntity() {
       // Includes the main entity's cache tags, since this entity references it.
       $this->entity->getCacheTags(),
       $this->getAdditionalCacheTagsForEntity($this->entity),
-      $view_cache_tag
+      $view_cache_tag,
+      ['rendered']
     );
     $non_referencing_entity_cache_tags = Cache::mergeTags(
       $this->non_referencing_entity->getCacheTags(),
-      \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags()
+      \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags(),
+      ['rendered']
     );
 
     // Generate the cache tags for all two possible entity listing paths.
@@ -311,44 +318,37 @@ public function testReferencedEntity() {
     // 2. list cache tag plus entity cache tag (listing query has a match)
     $empty_entity_listing_cache_tags = Cache::mergeTags(
       $this->entity->getEntityType()->getListCacheTags(),
-      $theme_cache_tags,
-      $render_cache_tags
+      $page_cache_tags
     );
     $nonempty_entity_listing_cache_tags = Cache::mergeTags(
       $this->entity->getEntityType()->getListCacheTags(),
       $this->entity->getCacheTags(),
       $this->getAdditionalCacheTagsForEntityListing($this->entity),
-      $theme_cache_tags,
-      $render_cache_tags
+      $page_cache_tags
     );
 
     $this->pass("Test referencing entity.", 'Debug');
     $this->verifyPageCache($referencing_entity_path, 'MISS');
     // Verify a cache hit, but also the presence of the correct cache tags.
-    $tags = Cache::mergeTags($render_cache_tags, $theme_cache_tags, $referencing_entity_cache_tags);
-    $this->verifyPageCache($referencing_entity_path, 'HIT', $tags);
+    $this->verifyPageCache($referencing_entity_path, 'HIT', Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags));
     // Also verify the existence of an entity render cache entry.
     $cid = 'entity_view:entity_test:' . $this->referencing_entity->id() . ':full:classy:r.anonymous:' . date_default_timezone_get();
-    $tags = Cache::mergeTags($render_cache_tags, $referencing_entity_cache_tags);
-    $this->verifyRenderCache($cid, $tags);
+    $this->verifyRenderCache($cid, $referencing_entity_cache_tags);
 
     $this->pass("Test non-referencing entity.", 'Debug');
     $this->verifyPageCache($non_referencing_entity_path, 'MISS');
     // Verify a cache hit, but also the presence of the correct cache tags.
-    $tags = Cache::mergeTags($render_cache_tags, $theme_cache_tags, $non_referencing_entity_cache_tags);
-    $this->verifyPageCache($non_referencing_entity_path, 'HIT', $tags);
+    $this->verifyPageCache($non_referencing_entity_path, 'HIT', Cache::mergeTags($non_referencing_entity_cache_tags, $page_cache_tags));
     // Also verify the existence of an entity render cache entry.
     $cid = 'entity_view:entity_test:' . $this->non_referencing_entity->id() . ':full:classy:r.anonymous:' . date_default_timezone_get();
-    $tags = Cache::mergeTags($render_cache_tags, $non_referencing_entity_cache_tags);
-    $this->verifyRenderCache($cid, $tags);
+    $this->verifyRenderCache($cid, $non_referencing_entity_cache_tags);
 
 
     $this->pass("Test listing of referencing entities.", 'Debug');
     // Prime the page cache for the listing of referencing entities.
     $this->verifyPageCache($listing_path, 'MISS');
     // Verify a cache hit, but also the presence of the correct cache tags.
-    $tags = Cache::mergeTags($render_cache_tags, $theme_cache_tags, $referencing_entity_cache_tags);
-    $this->verifyPageCache($listing_path, 'HIT', $tags);
+    $this->verifyPageCache($listing_path, 'HIT', Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags));
 
 
     $this->pass("Test empty listing.", 'Debug');
@@ -564,16 +564,13 @@ public function testReferencedEntity() {
     // Verify cache hits.
     $referencing_entity_cache_tags = Cache::mergeTags(
       $this->referencing_entity->getCacheTags(),
-      \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags()
+      \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags(),
+      ['rendered']
     );
-    $tags = Cache::mergeTags($render_cache_tags, $theme_cache_tags, $referencing_entity_cache_tags);
-    $this->verifyPageCache($referencing_entity_path, 'HIT', $tags);
-    $tags = Cache::mergeTags($render_cache_tags, $theme_cache_tags);
-    $this->verifyPageCache($listing_path, 'HIT', $tags);
-    $tags = Cache::mergeTags($render_cache_tags, $theme_cache_tags, $this->entity->getEntityType()->getListCacheTags());
-    $this->verifyPageCache($empty_entity_listing_path, 'HIT', $tags);
-    $tags = Cache::mergeTags($tags, $this->getAdditionalCacheTagsForEntityListing());
-    $this->verifyPageCache($nonempty_entity_listing_path, 'HIT', $tags);
+    $this->verifyPageCache($referencing_entity_path, 'HIT', Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags));
+    $this->verifyPageCache($listing_path, 'HIT', $page_cache_tags);
+    $this->verifyPageCache($empty_entity_listing_path, 'HIT', $empty_entity_listing_cache_tags);
+    $this->verifyPageCache($nonempty_entity_listing_path, 'HIT', Cache::mergeTags($this->entity->getEntityType()->getListCacheTags(), $this->getAdditionalCacheTagsForEntityListing(), $page_cache_tags));
   }
 
   /**
diff --git a/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php b/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
index 2a1ba6967e97..996d59b0767a 100644
--- a/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
+++ b/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
@@ -48,7 +48,7 @@ class MenuTreeStorageTest extends KernelTestBase {
   protected function setUp() {
     parent::setUp();
 
-    $this->treeStorage = new MenuTreeStorage($this->container->get('database'), $this->container->get('cache.menu'), 'menu_tree');
+    $this->treeStorage = new MenuTreeStorage($this->container->get('database'), $this->container->get('cache.menu'), $this->container->get('cache_tags.invalidator'), 'menu_tree');
     $this->connection = $this->container->get('database');
     $this->installEntitySchema('menu_link_content');
   }
@@ -74,7 +74,7 @@ protected function doTestEmptyStorage() {
   protected function doTestTable() {
     // Test that we can create a tree storage with an arbitrary table name and
     // that selecting from the storage creates the table.
-    $tree_storage = new MenuTreeStorage($this->container->get('database'), $this->container->get('cache.menu'), 'test_menu_tree');
+    $tree_storage = new MenuTreeStorage($this->container->get('database'), $this->container->get('cache.menu'), $this->container->get('cache_tags.invalidator'), 'test_menu_tree');
     $this->assertFalse($this->connection->schema()->tableExists('test_menu_tree'), 'Test table is not yet created');
     $tree_storage->countMenuLinks();
     $this->assertTrue($this->connection->schema()->tableExists('test_menu_tree'), 'Test table was created');
diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml
index e95c91d440cc..2a21015d3368 100644
--- a/core/modules/system/system.services.yml
+++ b/core/modules/system/system.services.yml
@@ -42,3 +42,8 @@ services:
     arguments: ['@cron', '@config.factory', '@state']
     tags:
       - { name: event_subscriber }
+  system.theme_settings_cache_tag:
+    class: Drupal\system\EventSubscriber\ThemeSettingsCacheTag
+    arguments: ['@theme_handler', '@cache_tags.invalidator']
+    tags:
+      - { name: event_subscriber }
diff --git a/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php b/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php
index c866e7901952..6fad81596f95 100644
--- a/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php
+++ b/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php
@@ -222,7 +222,7 @@ function testCacheClearByCacheTag() {
     // Assert that a cache tag in the toolbar cache under the key "user" exists
     // for adminUser against the language "en".
     $cache = $toolbarCache->get('toolbar_' . $admin_user_id . ':' . 'en');
-    $this->assertEqual($cache->tags[2], 'user:' . $admin_user_id, 'A cache tag in the toolbar cache under the key "user" exists for admin_user against the language "en".');
+    $this->assertEqual(in_array('user:' . $admin_user_id, $cache->tags), 'A cache tag in the toolbar cache under the key "user" exists for admin_user against the language "en".');
 
     // Assert that no toolbar cache exists for adminUser against the
     // language "fr".
@@ -242,7 +242,7 @@ function testCacheClearByCacheTag() {
     // Assert that a cache tag in the toolbar cache under the key "user" exists
     // for adminUser against the language "fr".
     $cache = $toolbarCache->get('toolbar_' . $admin_user_id . ':' . 'fr');
-    $this->assertEqual($cache->tags[2], 'user:' . $admin_user_id, 'A cache tag in the toolbar cache under the key "user" exists for admin_user against the language "fr".');
+    $this->assertEqual(in_array('user:' . $admin_user_id, $cache->tags), 'A cache tag in the toolbar cache under the key "user" exists for admin_user against the language "fr".');
 
     // Log in the adminUser2 user. We will use this user as a control to
     // verify that clearing a cache tag for adminUser does not clear the cache
@@ -255,7 +255,7 @@ function testCacheClearByCacheTag() {
     // Assert that a cache tag in the toolbar cache under the key "user" exists
     // for adminUser2 against the language "en".
     $cache = $toolbarCache->get('toolbar_' . $admin_user_2_id . ':' . 'en');
-    $this->assertEqual($cache->tags[2], 'user:' . $admin_user_2_id, 'A cache tag in the toolbar cache under the key "user" exists for admin_user_2 against the language "en".');
+    $this->assertEqual(in_array('user:' . $admin_user_2_id, $cache->tags), 'A cache tag in the toolbar cache under the key "user" exists for admin_user_2 against the language "en".');
 
     // Request a page in 'fr' to create the cache.
     $this->drupalGet('fr/test-page');
@@ -263,7 +263,7 @@ function testCacheClearByCacheTag() {
     // Assert that a cache tag in the toolbar cache under the key "user" exists
     // for adminUser against the language "fr".
     $cache = $toolbarCache->get('toolbar_' . $admin_user_2_id . ':' . 'fr');
-    $this->assertEqual($cache->tags[2], 'user:' . $admin_user_2_id, 'A cache tag in the toolbar cache under the key "user" exists for admin_user_2 against the language "fr".');
+    $this->assertEqual(in_array('user:' . $admin_user_2_id, $cache->tags), 'A cache tag in the toolbar cache under the key "user" exists for admin_user_2 against the language "fr".');
 
     // Log in the admin user and clear the caches for this user using a tag.
     $this->drupalLogin($this->adminUser);
@@ -285,12 +285,12 @@ function testCacheClearByCacheTag() {
     // Assert that a cache tag in the toolbar cache under the key "user" exists
     // for adminUser2 against the language "en".
     $cache = $toolbarCache->get('toolbar_' . $admin_user_2_id . ':' . 'en');
-    $this->assertEqual($cache->tags[2], 'user:' . $admin_user_2_id, 'A cache tag in the toolbar cache under the key "user" exists for admin_user_2 against the language "en".');
+    $this->assertEqual(in_array('user:' . $admin_user_2_id, $cache->tags), 'A cache tag in the toolbar cache under the key "user" exists for admin_user_2 against the language "en".');
 
     // Assert that a cache tag in the toolbar cache under the key "user" exists
     // for adminUser2 against the language "fr".
     $cache = $toolbarCache->get('toolbar_' . $admin_user_2_id . ':' . 'fr');
-    $this->assertEqual($cache->tags[2], 'user:' . $admin_user_2_id, 'A cache tag in the toolbar cache under the key "user" exists for admin_user_2 against the language "fr".');
+    $this->assertEqual(in_array('user:' . $admin_user_2_id, $cache->tags), 'A cache tag in the toolbar cache under the key "user" exists for admin_user_2 against the language "fr".');
   }
 
   /**
@@ -374,7 +374,7 @@ function testLocaleTranslationSubtreesHashCacheClear() {
     // Assert that a cache tag in the toolbar cache under the key "user" exists
     // for adminUser against the language "xx".
     $cache = $toolbarCache->get('toolbar_' . $admin_user_id . ':' . $langcode);
-    $this->assertEqual($cache->tags[2], 'user:' . $admin_user_id, 'A cache tag in the toolbar cache under the key "user" exists for admin_user against the language "xx".');
+    $this->assertEqual(in_array('user:' . $admin_user_id, $cache->tags), 'A cache tag in the toolbar cache under the key "user" exists for admin_user against the language "xx".');
 
     // Get a baseline hash for the admin menu subtrees before translating one
     // of the menu link items.
diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module
index 7b75e8940719..50704d01a130 100644
--- a/core/modules/toolbar/toolbar.module
+++ b/core/modules/toolbar/toolbar.module
@@ -314,7 +314,7 @@ function _toolbar_get_subtrees_hash($langcode) {
     // Clear the cache when the 'locale' tag is deleted. This ensures a fresh
     // subtrees rendering when string translations are made.
     $role_list_cache_tags = \Drupal::entityManager()->getDefinition('user_role')->getListCacheTags();
-    \Drupal::cache('toolbar')->set($cid, $hash, Cache::PERMANENT, Cache::mergeTags(array('user:' . $uid, 'locale', 'menu:admin'), $role_list_cache_tags));
+    \Drupal::cache('toolbar')->set($cid, $hash, Cache::PERMANENT, Cache::mergeTags(array('user:' . $uid, 'locale', 'config:system.menu.admin'), $role_list_cache_tags));
   }
   return $hash;
 }
diff --git a/core/modules/tour/src/Tests/TourCacheTagsTest.php b/core/modules/tour/src/Tests/TourCacheTagsTest.php
index 085477aea6db..d6a537fdc5d5 100644
--- a/core/modules/tour/src/Tests/TourCacheTagsTest.php
+++ b/core/modules/tour/src/Tests/TourCacheTagsTest.php
@@ -48,12 +48,10 @@ public function testRenderedTour() {
     $this->verifyPageCache($path, 'MISS');
 
     // Verify a cache hit, but also the presence of the correct cache tags.
-    $expected_tags = array(
-      'theme:classy',
-      'theme_global_settings',
-      'tour:tour-test',
+    $expected_tags = [
+      'config:tour.tour.tour-test',
       'rendered',
-    );
+    ];
     $this->verifyPageCache($path, 'HIT', $expected_tags);
 
     // Verify that after modifying the tour, there is a cache miss.
@@ -70,7 +68,10 @@ public function testRenderedTour() {
     $this->verifyPageCache($path, 'MISS');
 
     // Verify a cache hit.
-    $this->verifyPageCache($path, 'HIT', array('rendered', 'theme:classy', 'theme_global_settings'));
+    $expected_tags = [
+      'rendered',
+    ];
+    $this->verifyPageCache($path, 'HIT', $expected_tags);
   }
 
 }
diff --git a/core/modules/user/src/Access/RegisterAccessCheck.php b/core/modules/user/src/Access/RegisterAccessCheck.php
index 0f0a3f597acd..ee0e5beb5bb1 100644
--- a/core/modules/user/src/Access/RegisterAccessCheck.php
+++ b/core/modules/user/src/Access/RegisterAccessCheck.php
@@ -26,7 +26,7 @@ class RegisterAccessCheck implements AccessInterface {
    *   The access result.
    */
   public function access(AccountInterface $account) {
-    // @todo cacheable per role once https://www.drupal.org/node/2040135 lands.
-    return AccessResult::allowedIf($account->isAnonymous() && \Drupal::config('user.settings')->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY)->setCacheable(FALSE);
+    $user_settings = \Drupal::config('user.settings');
+    return AccessResult::allowedIf($account->isAnonymous() && $user_settings->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY)->cacheUntilConfigurationChanges($user_settings);
   }
 }
diff --git a/core/modules/user/src/PermissionsHash.php b/core/modules/user/src/PermissionsHash.php
index 3f7264541e4d..82ac991ffd07 100644
--- a/core/modules/user/src/PermissionsHash.php
+++ b/core/modules/user/src/PermissionsHash.php
@@ -59,7 +59,7 @@ public function generate(AccountInterface $account) {
     }
     else {
       $permissions_hash = $this->doGenerate($sorted_roles);
-      $tags = Cache::buildTags('user_role', $sorted_roles);
+      $tags = Cache::buildTags('config:user.role', $sorted_roles, '.');
       $this->cache->set("user_permissions_hash:$role_list", $permissions_hash, Cache::PERMANENT, $tags);
     }
 
diff --git a/core/modules/user/src/Tests/UserPictureTest.php b/core/modules/user/src/Tests/UserPictureTest.php
index 13feb0f3370c..94c4a2f85ae1 100644
--- a/core/modules/user/src/Tests/UserPictureTest.php
+++ b/core/modules/user/src/Tests/UserPictureTest.php
@@ -103,9 +103,6 @@ function testPictureOnNodeComment() {
       ->set('features.comment_user_picture', TRUE)
       ->save();
 
-    // @todo Remove when https://www.drupal.org/node/2040135 lands.
-    Cache::invalidateTags(['rendered']);
-
     $edit = array(
       'comment_body[0][value]' => $this->randomString(),
     );
@@ -118,9 +115,6 @@ function testPictureOnNodeComment() {
       ->set('features.comment_user_picture', FALSE)
       ->save();
 
-    // @todo Remove when https://www.drupal.org/node/2040135 lands.
-    Cache::invalidateTags(['rendered']);
-
     $this->drupalGet('node/' . $node->id());
     $this->assertNoRaw(file_uri_target($file->getFileUri()), 'User picture not found on node and comment.');
   }
diff --git a/core/modules/user/src/Tests/UserSignatureTest.php b/core/modules/user/src/Tests/UserSignatureTest.php
index 4fa92d8a964d..b70c91de463b 100644
--- a/core/modules/user/src/Tests/UserSignatureTest.php
+++ b/core/modules/user/src/Tests/UserSignatureTest.php
@@ -128,7 +128,7 @@ function testUserSignature() {
     $this->assertRaw(check_markup($signature_text, $this->filtered_html_format->id()), 'Filtered signature text found.');
     // Verify that the user signature's text format's cache tag is present.
     $this->drupalGet('node/' . $node->id());
-    $this->assertTrue(in_array('filter_format:filtered_html_format', explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'))));
+    $this->assertTrue(in_array('config:filter.format.filtered_html_format', explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'))));
 
     // Verify the signature field is available on Manage form display page.
     $this->config('user.settings')->set('signatures', 0)->save();
diff --git a/core/modules/views/src/Plugin/ViewsHandlerManager.php b/core/modules/views/src/Plugin/ViewsHandlerManager.php
index ac7abda6271c..9d3fda472022 100644
--- a/core/modules/views/src/Plugin/ViewsHandlerManager.php
+++ b/core/modules/views/src/Plugin/ViewsHandlerManager.php
@@ -59,7 +59,7 @@ public function __construct($handler_type, \Traversable $namespaces, ViewsData $
     }
     parent::__construct("Plugin/views/$handler_type", $namespaces, $module_handler, $plugin_interface, $plugin_definition_annotation_name);
 
-    $this->setCacheBackend($cache_backend, "views:$handler_type", array('extension', 'extension:views'));
+    $this->setCacheBackend($cache_backend, "views:$handler_type", array('config:core.extension', 'extension:views'));
     $this->alterInfo('views_plugins_' . $handler_type);
 
     $this->viewsData = $views_data;
diff --git a/core/modules/views/src/Plugin/ViewsPluginManager.php b/core/modules/views/src/Plugin/ViewsPluginManager.php
index 924cc35ca796..efb9061fcffe 100644
--- a/core/modules/views/src/Plugin/ViewsPluginManager.php
+++ b/core/modules/views/src/Plugin/ViewsPluginManager.php
@@ -43,7 +43,7 @@ public function __construct($type, \Traversable $namespaces, CacheBackendInterfa
     );
 
     $this->alterInfo('views_plugins_' . $type);
-    $this->setCacheBackend($cache_backend, "views:$type", array('extension', 'extension:views'));
+    $this->setCacheBackend($cache_backend, "views:$type", array('config:core.extension', 'extension:views'));
   }
 
 }
diff --git a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php
index 163dc44346dd..bce200d8d51c 100644
--- a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php
+++ b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php
@@ -205,8 +205,7 @@ public function cacheGet($type) {
    * to be sure that we catch everything. Maybe that's a bad idea.
    */
   public function cacheFlush() {
-    $id = $this->view->storage->id();
-    Cache::invalidateTags(array('view:' . $id));
+    Cache::invalidateTags($this->view->storage->getCacheTags());
   }
 
   /**
@@ -323,12 +322,11 @@ public function generateOutputKey() {
   /**
    * Gets an array of cache tags for the current view.
    *
-   * @return array
-   *   An array fo cache tags based on the current view.
+   * @return string[]
+   *   An array of cache tags based on the current view.
    */
   protected function getCacheTags() {
-    $id = $this->view->storage->id();
-    $tags = array('view:' . $id);
+    $tags = $this->view->storage->getCacheTags();
 
     $entity_information = $this->view->query->getEntityTableInfo();
 
diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
index 496873f79553..47ad07dd7b87 100644
--- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
+++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
@@ -202,8 +202,7 @@ public function initDisplay(ViewExecutable $view, array &$display, array &$optio
         }
         else {
           $this->unpackOptions($this->options, $options);
-          $id = $this->view->storage->id();
-          \Drupal::cache('data')->set($cid, $this->options, Cache::PERMANENT, array('extension', 'extension:views', 'view:' . $id));
+          \Drupal::cache('data')->set($cid, $this->options, Cache::PERMANENT, Cache::mergeTags(array('config:core.extension', 'extension:views'), $this->view->storage->getCacheTags()));
         }
         static::$unpackOptions[$cid] = $this->options;
       }
diff --git a/core/modules/views/src/ViewsData.php b/core/modules/views/src/ViewsData.php
index c90c69e55f9e..81469e7724dd 100644
--- a/core/modules/views/src/ViewsData.php
+++ b/core/modules/views/src/ViewsData.php
@@ -200,7 +200,7 @@ protected function cacheGet($cid) {
    *   The data that will be cached.
    */
   protected function cacheSet($cid, $data) {
-    return $this->cacheBackend->set($this->prepareCid($cid), $data, Cache::PERMANENT, array('views_data', 'extension', 'extension:views'));
+    return $this->cacheBackend->set($this->prepareCid($cid), $data, Cache::PERMANENT, array('views_data', 'config:core.extension', 'extension:views'));
   }
 
   /**
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index 966d0e85ae60..d8516be19a9f 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -470,7 +470,7 @@ function views_field_config_create(FieldConfigInterface $field) {
  * Implements hook_ENTITY_TYPE_update() for 'field_config'.
  */
 function views_field_config_update(FieldConfigInterface $field) {
-  Cache::invalidateTags(array('extension' => 'views'));
+  Cache::invalidateTags(['extension:views']);
   \Drupal::cache('render')->deleteAll();
 }
 
@@ -478,7 +478,7 @@ function views_field_config_update(FieldConfigInterface $field) {
  * Implements hook_ENTITY_TYPE_delete() for 'field_config'.
  */
 function views_field_config_delete(FieldConfigInterface $field) {
-  Cache::invalidateTags(array('extension' => 'views'));
+  Cache::invalidateTags(['extension:views']);
   \Drupal::cache('render')->deleteAll();
 }
 
@@ -487,7 +487,7 @@ function views_field_config_delete(FieldConfigInterface $field) {
  */
 function views_invalidate_cache() {
   // Clear Views' info cache entries.
-  Cache::invalidateTags(array('extension' => 'views'));
+  Cache::invalidateTags(['extension:views']);
 
   // Set the menu as needed to be rebuilt.
   \Drupal::service('router.builder_indicator')->setRebuildNeeded();
diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php
index 71fcf90b7263..e2819d6b5fd7 100644
--- a/core/modules/views_ui/src/ViewUI.php
+++ b/core/modules/views_ui/src/ViewUI.php
@@ -1158,7 +1158,7 @@ public function getDependencies() {
    * {@inheritdoc}
    */
   public function getCacheTags() {
-    $this->storage->getCacheTags();
+    return $this->storage->getCacheTags();
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
index 98cffaf64c35..ec1cdd6599db 100644
--- a/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
@@ -555,7 +555,7 @@ public function testCheckNamedRouteWithNonExistingRoute() {
     $this->setupAccessChecker();
 
     $this->assertEquals(FALSE, $this->accessManager->checkNamedRoute('test_route_1', array(), $this->account), 'A non existing route lead to access.');
-    $this->assertEquals(AccessResult::forbidden()->addCacheTags(array('extension')), $this->accessManager->checkNamedRoute('test_route_1', array(), $this->account, TRUE), 'A non existing route lead to access.');
+    $this->assertEquals(AccessResult::forbidden()->addCacheTags(['config:core.extension']), $this->accessManager->checkNamedRoute('test_route_1', array(), $this->account, TRUE), 'A non existing route lead to access.');
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php b/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php
index 1213614f5322..da5651c6ee5e 100644
--- a/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php
@@ -400,6 +400,7 @@ public function testCacheContexts() {
    * @covers ::resetCacheTags
    * @covers ::getCacheTags
    * @covers ::cacheUntilEntityChanges
+   * @covers ::cacheUntilConfigurationChanges
    */
   public function testCacheTags() {
     $verify = function (AccessResult $access, array $tags) {
@@ -447,6 +448,18 @@ public function testCacheTags() {
     $b = AccessResult::neutral()->cacheUntilEntityChanges($node);
     $verify($b, $tags);
     $this->assertEquals($a, $b);
+
+    // ::cacheUntilConfigurationChanges() convenience method.
+    $configuration = $this->getMock('\Drupal\Core\Config\ConfigBase');
+    $configuration->expects($this->any())
+      ->method('getCacheTags')
+      ->will($this->returnValue(array('config:foo.bar.baz')));
+    $tags = array('config:foo.bar.baz');
+    $a = AccessResult::neutral()->addCacheTags($tags);
+    $verify($a, $tags);
+    $b = AccessResult::neutral()->cacheUntilConfigurationChanges($configuration);
+    $verify($b, $tags);
+    $this->assertEquals($a, $b);
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheTest.php
index ea30f1a4826d..75362a7a9517 100644
--- a/core/tests/Drupal/Tests/Core/Cache/CacheTest.php
+++ b/core/tests/Drupal/Tests/Core/Cache/CacheTest.php
@@ -105,6 +105,8 @@ public function buildTagsProvider() {
       ['node', [5 => NULL], ['node:']],
       ['node', ['a' => NULL], ['node:']],
       ['node', ['a' => TRUE], ['node:1']],
+      // Test the $glue parameter.
+      ['config:system.menu', ['menu_name'], ['config:system.menu.menu_name'], '.'],
     ];
   }
 
@@ -113,8 +115,8 @@ public function buildTagsProvider() {
    *
    * @dataProvider buildTagsProvider
    */
-  public function testBuildTags($prefix, array $suffixes, array $expected) {
-    $this->assertEquals($expected, Cache::buildTags($prefix, $suffixes));
+  public function testBuildTags($prefix, array $suffixes, array $expected, $glue = ':') {
+    $this->assertEquals($expected, Cache::buildTags($prefix, $suffixes, $glue));
   }
 
 }
diff --git a/core/tests/Drupal/Tests/Core/Config/ConfigFactoryTest.php b/core/tests/Drupal/Tests/Core/Config/ConfigFactoryTest.php
new file mode 100644
index 000000000000..95bd5705d7dc
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Config/ConfigFactoryTest.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Drupal\Tests\Core\Config;
+
+use Drupal\Core\Config\Config;
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * @group Config
+ */
+class ConfigFactoryTest extends UnitTestCase {
+
+  /**
+   * Config factory under test.
+   *
+   * @var \Drupal\Core\Config\ConfigFactory
+   */
+  protected $configFactory;
+
+  /**
+   * Storage.
+   *
+   * @var \Drupal\Core\Config\StorageInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $storage;
+
+  /**
+   * Event Dispatcher.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $eventDispatcher;
+
+  /**
+   * Typed Config.
+   *
+   * @var \Drupal\Core\Config\TypedConfigManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $typedConfig;
+
+  /**
+   * The mocked cache tags invalidator.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cacheTagsInvalidator;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    $this->storage = $this->getMock('Drupal\Core\Config\StorageInterface');
+    $this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
+    $this->typedConfig = $this->getMock('\Drupal\Core\Config\TypedConfigManagerInterface');
+    $this->configFactory = new ConfigFactory($this->storage, $this->eventDispatcher, $this->typedConfig);
+
+    $this->cacheTagsInvalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
+
+    $container = new ContainerBuilder();
+    $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator);
+    \Drupal::setContainer($container);
+  }
+
+  /**
+   * @covers ::rename
+   */
+  public function testRename() {
+    $old = new Config($this->randomMachineName(), $this->storage, $this->eventDispatcher, $this->typedConfig);
+    $new = new Config($this->randomMachineName(), $this->storage, $this->eventDispatcher, $this->typedConfig);
+
+    $this->storage->expects($this->exactly(2))
+      ->method('readMultiple')
+      ->willReturnMap([
+        [[$old->getName()], $old->getRawData()],
+        [[$new->getName()], $new->getRawData()],
+      ]);
+
+    $this->cacheTagsInvalidator->expects($this->once())
+      ->method('invalidateTags')
+      ->with($old->getCacheTags());
+
+    $this->storage->expects($this->once())
+      ->method('rename')
+      ->with($old->getName(), $new->getName());
+
+    $this->configFactory->rename($old->getName(), $new->getName());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Config/ConfigTest.php b/core/tests/Drupal/Tests/Core/Config/ConfigTest.php
index 4111e081673d..936287ade329 100644
--- a/core/tests/Drupal/Tests/Core/Config/ConfigTest.php
+++ b/core/tests/Drupal/Tests/Core/Config/ConfigTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Core\Config;
 
+use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Tests\UnitTestCase;
 use Drupal\Core\Config\Config;
 use Drupal\Component\Utility\String;
@@ -50,11 +51,23 @@ class ConfigTest extends UnitTestCase {
    */
   protected $typedConfig;
 
+  /**
+   * The mocked cache tags invalidator.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cacheTagsInvalidator;
+
   public function setUp() {
     $this->storage = $this->getMock('Drupal\Core\Config\StorageInterface');
     $this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
     $this->typedConfig = $this->getMock('\Drupal\Core\Config\TypedConfigManagerInterface');
     $this->config = new Config('config.test', $this->storage, $this->eventDispatcher, $this->typedConfig);
+    $this->cacheTagsInvalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
+
+    $container = new ContainerBuilder();
+    $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator);
+    \Drupal::setContainer($container);
   }
 
   /**
@@ -117,7 +130,10 @@ public function testSetData($data) {
    * @covers ::save
    * @dataProvider nestedDataProvider
    */
-  public function testSave($data) {
+  public function testSaveNew($data) {
+    $this->cacheTagsInvalidator->expects($this->never())
+      ->method('invalidateTags');
+
     // Set initial data.
     $this->config->setData($data);
 
@@ -136,6 +152,27 @@ public function testSave($data) {
     $this->assertOriginalConfigDataEquals($data, TRUE);
   }
 
+  /**
+   * @covers ::save
+   * @dataProvider nestedDataProvider
+   */
+  public function testSaveExisting($data) {
+    $this->cacheTagsInvalidator->expects($this->once())
+      ->method('invalidateTags')
+      ->with(['config:config.test']);
+
+    // Set initial data.
+    $this->config->setData($data);
+    $this->config->save();
+
+    // Update.
+    $new_data = $data;
+    $new_data['a']['d'] = 2;
+    $this->config->setData($new_data);
+    $this->config->save();
+    $this->assertOriginalConfigDataEquals($new_data, TRUE);
+  }
+
   /**
    * @covers ::setModuleOverride
    * @covers ::setSettingsOverride
@@ -276,6 +313,10 @@ public function testNestedClear($data) {
    * @dataProvider overrideDataProvider
    */
   public function testDelete($data, $module_data) {
+    $this->cacheTagsInvalidator->expects($this->once())
+      ->method('invalidateTags')
+      ->with(['config:config.test']);
+
     // Set initial data.
     foreach ($data as $key => $value) {
       $this->config->set($key, $value);
@@ -355,6 +396,13 @@ public function testValidateNameException($name, $exception_message) {
     $this->config->validateName($name);
   }
 
+  /**
+   * @covers ::getCacheTags
+   */
+  public function testGetCacheTags() {
+    $this->assertSame(['config:' . $this->config->getName()], $this->config->getCacheTags());
+  }
+
   /**
    * Provides data to test name validation.
    *
diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php
index 0480f52ae10a..7af62e5db273 100644
--- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php
@@ -106,6 +106,9 @@ protected function setUp() {
     $this->entityType->expects($this->any())
       ->method('getProvider')
       ->will($this->returnValue($this->provider));
+    $this->entityType->expects($this->any())
+      ->method('getConfigPrefix')
+      ->willReturn('test_provider.' . $this->entityTypeId);
 
     $this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface');
     $this->entityManager->expects($this->any())
@@ -362,7 +365,7 @@ public function testEnable() {
   public function testDisable() {
     $this->cacheTagsInvalidator->expects($this->once())
       ->method('invalidateTags')
-      ->with(array($this->entityTypeId . ':' . $this->id));
+      ->with(array('config:test_provider.'  . $this->entityTypeId . '.' . $this->id));
 
     $this->entity->setStatus(TRUE);
     $this->assertSame($this->entity, $this->entity->disable());
diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php
index 13438a8bf519..f7eef52f0a35 100644
--- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php
+++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php
@@ -302,8 +302,9 @@ public function testSaveUpdate(EntityInterface $entity) {
     $this->cacheTagsInvalidator->expects($this->once())
       ->method('invalidateTags')
       ->with(array(
-        $this->entityTypeId . ':foo', // Own cache tag.
-        $this->entityTypeId . '_list', // List cache tag.
+        // List cache tag only; the own cache tag is invalidated by the config
+        // system.
+        $this->entityTypeId . '_list',
       ));
 
     $this->configFactory->expects($this->exactly(2))
@@ -362,8 +363,9 @@ public function testSaveRename(ConfigEntityInterface $entity) {
     $this->cacheTagsInvalidator->expects($this->once())
       ->method('invalidateTags')
       ->with(array(
-        $this->entityTypeId .':bar', // Own cache tag.
-        $this->entityTypeId . '_list', // List cache tag.
+        // List cache tag only; the own cache tag is invalidated by the config
+        // system.
+        $this->entityTypeId . '_list',
       ));
 
     $this->configFactory->expects($this->once())
@@ -728,9 +730,9 @@ public function testDelete() {
     $this->cacheTagsInvalidator->expects($this->once())
       ->method('invalidateTags')
       ->with(array(
-        $this->entityTypeId . ':bar', // Own cache tag.
-        $this->entityTypeId . ':foo', // Own cache tag.
-        $this->entityTypeId . '_list', // List cache tag.
+        // List cache tag only; the own cache tag is invalidated by the config
+        // system.
+        $this->entityTypeId . '_list',
       ));
 
     $this->configFactory->expects($this->exactly(2))
-- 
GitLab