diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
index e88025c..1675ffe 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
@@ -1669,25 +1669,32 @@ public function finalizePurge(FieldStorageDefinitionInterface $storage_definitio
    * {@inheritdoc}
    */
   public function countFieldData($storage_definition, $as_bool = FALSE) {
-    $table_mapping = $this->getTableMapping();
+    $count = 0;
 
-    $is_deleted = $this->storageDefinitionIsDeleted($storage_definition);
-    $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $is_deleted);
-    $query = $this->database->select($table_name, 't');
-    $or = $query->orConditionGroup();
-    foreach ($storage_definition->getColumns() as $column_name => $data) {
-      $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $column_name));
-    }
-    $query
-      ->condition($or)
-      ->fields('t', array('entity_id'))
-      ->distinct(TRUE);
-    // If we are performing the query just to check if the field has data
-    // limit the number of rows.
-    if ($as_bool) {
-      $query->range(0, 1);
+    // @todo Find a way to count field data also for fields having custom
+    //   storage. See https://www.drupal.org/node/2337753.
+    if (!$storage_definition->hasCustomStorage()) {
+      $table_mapping = $this->getTableMapping();
+      $is_deleted = $this->storageDefinitionIsDeleted($storage_definition);
+      $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $is_deleted);
+
+      $query = $this->database->select($table_name, 't');
+      $or = $query->orConditionGroup();
+      foreach ($storage_definition->getColumns() as $column_name => $data) {
+        $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $column_name));
+      }
+      $query
+        ->condition($or)
+        ->fields('t', array('entity_id'))
+        ->distinct(TRUE);
+      // If we are performing the query just to check if the field has data
+      // limit the number of rows.
+      if ($as_bool) {
+        $query->range(0, 1);
+      }
+      $count = $query->countQuery()->execute()->fetchField();
     }
-    $count = $query->countQuery()->execute()->fetchField();
+
     return $as_bool ? (bool) $count : (int) $count;
   }
 
@@ -1746,7 +1753,7 @@ public static function _fieldSqlSchema(FieldStorageDefinitionInterface $storage_
         'description' => 'The entity id this data is attached to',
       );
     }
-    else {
+    elseif ($table_mapping->allowsSharedTableStorage($storage_definition)) {
       $id_schema = array(
         'type' => 'varchar',
         'length' => 128,
diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module
index 7361345..f8aaad1 100644
--- a/core/modules/aggregator/aggregator.module
+++ b/core/modules/aggregator/aggregator.module
@@ -11,11 +11,6 @@
 use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
- * Denotes that a feed's items should never expire.
- */
-const AGGREGATOR_CLEAR_NEVER = 0;
-
-/**
  * Implements hook_help().
  */
 function aggregator_help($route_name, RouteMatchInterface $route_match) {
diff --git a/core/modules/aggregator/src/Entity/Feed.php b/core/modules/aggregator/src/Entity/Feed.php
index 5a9317a..09b4c3d 100644
--- a/core/modules/aggregator/src/Entity/Feed.php
+++ b/core/modules/aggregator/src/Entity/Feed.php
@@ -50,6 +50,11 @@
 class Feed extends ContentEntityBase implements FeedInterface {
 
   /**
+   * Denotes that a feed's items should never expire.
+   */
+  const CLEAR_NEVER = 0;
+
+  /**
    * Implements Drupal\Core\Entity\EntityInterface::label().
    */
   public function label() {
@@ -165,7 +170,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
 
     $intervals = array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200);
     $period = array_map(array(\Drupal::service('date.formatter'), 'formatInterval'), array_combine($intervals, $intervals));
-    $period[AGGREGATOR_CLEAR_NEVER] = t('Never');
+    $period[static::CLEAR_NEVER] = t('Never');
 
     $fields['refresh'] = BaseFieldDefinition::create('list_integer')
       ->setLabel(t('Update interval'))
diff --git a/core/modules/aggregator/src/FeedStorage.php b/core/modules/aggregator/src/FeedStorage.php
index 9017974..b34d74d 100644
--- a/core/modules/aggregator/src/FeedStorage.php
+++ b/core/modules/aggregator/src/FeedStorage.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\aggregator;
 
+use Drupal\aggregator\Entity\Feed;
 use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
 
 /**
@@ -41,7 +42,7 @@ public function getFeedDuplicates(FeedInterface $feed) {
   public function getFeedIdsToRefresh() {
     return $this->database->query('SELECT fid FROM {aggregator_feed} WHERE queued = 0 AND checked + refresh < :time AND refresh <> :never', array(
       ':time' => REQUEST_TIME,
-      ':never' => AGGREGATOR_CLEAR_NEVER
+      ':never' => Feed::CLEAR_NEVER
     ))->fetchCol();
   }
 
diff --git a/core/modules/aggregator/src/Plugin/aggregator/processor/DefaultProcessor.php b/core/modules/aggregator/src/Plugin/aggregator/processor/DefaultProcessor.php
index 642646a..465213c 100644
--- a/core/modules/aggregator/src/Plugin/aggregator/processor/DefaultProcessor.php
+++ b/core/modules/aggregator/src/Plugin/aggregator/processor/DefaultProcessor.php
@@ -7,11 +7,11 @@
 
 namespace Drupal\aggregator\Plugin\aggregator\processor;
 
+use Drupal\aggregator\Entity\Feed;
+use Drupal\aggregator\FeedInterface;
 use Drupal\aggregator\ItemStorageInterface;
 use Drupal\aggregator\Plugin\AggregatorPluginSettingsBase;
 use Drupal\aggregator\Plugin\ProcessorInterface;
-use Drupal\aggregator\FeedInterface;
-use Drupal\Core\Database\Database;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Datetime\DateFormatter;
 use Drupal\Core\Entity\Query\QueryInterface;
@@ -115,7 +115,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
     }, array_combine($counts, $counts));
     $intervals = array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800);
     $period = array_map(array($this->dateFormatter, 'formatInterval'), array_combine($intervals, $intervals));
-    $period[AGGREGATOR_CLEAR_NEVER] = t('Never');
+    $period[Feed::CLEAR_NEVER] = t('Never');
 
     $form['processors'][$info['id']] = array();
     // Only wrap into details if there is a basic configuration.
@@ -246,7 +246,7 @@ public function delete(FeedInterface $feed) {
   public function postProcess(FeedInterface $feed) {
     $aggregator_clear = $this->configuration['items']['expire'];
 
-    if ($aggregator_clear != AGGREGATOR_CLEAR_NEVER) {
+    if ($aggregator_clear != Feed::CLEAR_NEVER) {
       // Delete all items that are older than flush item timer.
       $age = REQUEST_TIME - $aggregator_clear;
       $result = $this->itemQuery
diff --git a/core/modules/aggregator/src/Tests/FeedParserTest.php b/core/modules/aggregator/src/Tests/FeedParserTest.php
index 7489e9f..38ada09 100644
--- a/core/modules/aggregator/src/Tests/FeedParserTest.php
+++ b/core/modules/aggregator/src/Tests/FeedParserTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\aggregator\Tests;
 
+use Drupal\aggregator\Entity\Feed;
 use Zend\Feed\Reader\Reader;
 
 /**
@@ -20,7 +21,7 @@ protected function setUp() {
     // Do not delete old aggregator items during these tests, since our sample
     // feeds have hardcoded dates in them (which may be expired when this test
     // is run).
-    $this->container->get('config.factory')->get('aggregator.settings')->set('items.expire', AGGREGATOR_CLEAR_NEVER)->save();
+    $this->container->get('config.factory')->get('aggregator.settings')->set('items.expire', Feed::CLEAR_NEVER)->save();
     // Reset any reader cache between tests.
     Reader::reset();
     // Set our bridge extension manager to Zend Feed.
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 71297db..8e669f0 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -78,7 +78,7 @@ function hook_cron() {
   // Fetch feeds from other sites.
   $result = db_query('SELECT * FROM {aggregator_feed} WHERE checked + refresh < :time AND refresh <> :never', array(
     ':time' => REQUEST_TIME,
-    ':never' => AGGREGATOR_CLEAR_NEVER,
+    ':never' => Feed::CLEAR_NEVER,
   ));
   $queue = \Drupal::queue('aggregator_feeds');
   foreach ($result as $feed) {
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 6ca3699..b3658ed 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -5,20 +5,23 @@
  * Configuration system that lets administrators modify the workings of the site.
  */
 
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
+use Drupal\Core\Block\BlockPluginInterface;
 use Drupal\Core\Cache\Cache;
+use Drupal\Core\Entity\FieldableEntityStorageInterface;
+use Drupal\Core\Database\DatabaseExceptionWrapper;
+use Drupal\Core\Entity\Query\QueryException;
 use Drupal\Core\Extension\Extension;
 use Drupal\Core\Extension\ExtensionDiscovery;
-use Drupal\Core\Form\FormState;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Routing\RouteMatchInterface;
-use Drupal\Core\StringTranslation\TranslationWrapper;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Menu\MenuTreeParameters;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\StringTranslation\TranslationWrapper;
 use Drupal\Core\Url;
-use Drupal\Core\Block\BlockPluginInterface;
 use Drupal\user\UserInterface;
-use Symfony\Component\HttpFoundation\RedirectResponse;
 use GuzzleHttp\Exception\RequestException;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * New users will be set to the default time zone at registration.
@@ -991,11 +994,45 @@ function system_sort_themes($a, $b) {
  * Implements hook_system_info_alter().
  */
 function system_system_info_alter(&$info, Extension $file, $type) {
-  // Remove page-top and page-bottom from the blocks UI since they are reserved for
-  // modules to populate from outside the blocks system.
-  if ($type == 'theme') {
-    $info['regions_hidden'][] = 'page_top';
-    $info['regions_hidden'][] = 'page_bottom';
+  switch ($type) {
+    case 'theme':
+      // Remove page-top and page-bottom from the blocks UI since they are
+      // reserved for modules to populate from outside the blocks system.
+      $info['regions_hidden'][] = 'page_top';
+      $info['regions_hidden'][] = 'page_bottom';
+      break;
+
+    case 'module':
+      // @todo Unify this with field_system_info_alter() once purging is
+      //   supported for any field. See https://www.drupal.org/node/2282119.
+      $module_name = $file->getName();
+      if ($module_name != 'field') {
+        $entity_manager = \Drupal::entityManager();
+        foreach ($entity_manager->getDefinitions() as $entity_type_id => $entity_type) {
+          try {
+            // We skip entity-defining modules, otherwise deleting all entities
+            // would be required before being able to uninstall them.
+            if ($entity_type->getProvider() != $module_name && $entity_type->isFieldable()) {
+              $storage = $entity_manager->getStorage($entity_type_id);
+              if ($storage instanceof FieldableEntityStorageInterface) {
+                foreach ($entity_manager->getFieldStorageDefinitions($entity_type_id) as $storage_definition) {
+                  if ($storage_definition->getProvider() == $module_name && $storage->countFieldData($storage_definition, TRUE)) {
+                    $info['required'] = TRUE;
+                    $info['explanation'] = t('Fields type(s) in use');
+                    break;
+                  }
+                }
+              }
+            }
+          }
+          catch (PluginNotFoundException $e) {
+            // This may happen if the current module is being installed. In this
+            // case we can safely ignore it, as we are interested in blocking
+            // module uninstallation.
+          }
+        }
+      }
+      break;
   }
 }
 
