diff --git a/core/core.services.yml b/core/core.services.yml
index a1e997e..e021ec2 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -81,6 +81,7 @@ services:
     class: Drupal\Core\Config\ConfigFactory
     tags:
       - { name: event_subscriber }
+      - { name: service_collector, tag: 'config.factory.override', call: addOverride }
     arguments: ['@config.storage', '@event_dispatcher', '@config.typed']
   config.installer:
     class: Drupal\Core\Config\ConfigInstaller
@@ -169,6 +170,8 @@ services:
     arguments: ['@path.alias_storage', '@path.alias_whitelist', '@language_manager']
   http_client:
     class: Drupal\Core\Http\Client
+    tags:
+      - { name: service_collector, tag: http_client_subscriber, call: attach }
   http_client_simpletest_subscriber:
     class: Drupal\Core\Http\Plugin\SimpletestHttpRequestSubscriber
     tags:
@@ -176,6 +179,8 @@ services:
   theme.negotiator:
     class: Drupal\Core\Theme\ThemeNegotiator
     arguments: ['@access_check.theme', '@request_stack']
+    tags:
+      - { name: service_collector, tag: theme_negotiator, call: addNegotiator }
   theme.negotiator.default:
     class: Drupal\Core\Theme\DefaultNegotiator
     arguments: ['@config.factory']
@@ -276,6 +281,8 @@ services:
     arguments: ['@language_manager']
     calls:
       - [initLanguageManager]
+    tags:
+      - { name: service_collector, tag: string_translator, call: addTranslator }
   database.slave:
     class: Drupal\Core\Database\Connection
     factory_class: Drupal\Core\Database\Database
@@ -321,6 +328,8 @@ services:
     arguments: ['@router.route_provider']
     calls:
       - [setFinalMatcher, ['@router.matcher.final_matcher']]
+    tags:
+      - { name: service_collector, tag: route_filter, call: addRouteFilter }
   url_generator:
     class: Drupal\Core\Routing\UrlGenerator
     arguments: ['@router.route_provider', '@path_processor_manager', '@route_processor_manager', '@config.factory', '@settings']
@@ -333,6 +342,8 @@ services:
   router.dynamic:
     class: Symfony\Cmf\Component\Routing\DynamicRouter
     arguments: ['@router.request_context', '@router.matcher', '@url_generator']
+    tags:
+      - { name: service_collector, tag: route_enhancer, call: addRouteEnhancer }
   router:
     class: Symfony\Cmf\Component\Routing\ChainRouter
     calls:
@@ -595,8 +606,13 @@ services:
     arguments: [['@exception_controller', execute]]
   route_processor_manager:
     class: Drupal\Core\RouteProcessor\RouteProcessorManager
+    tags:
+      - { name: service_collector, tag: route_processor_outbound, call: addOutbound }
   path_processor_manager:
     class: Drupal\Core\PathProcessor\PathProcessorManager
+    tags:
+      - { name: service_collector, tag: path_processor_inbound, call: addInbound }
+      - { name: service_collector, tag: path_processor_outbound, call: addOutbound }
   path_processor_decode:
     class: Drupal\Core\PathProcessor\PathProcessorDecode
     tags:
@@ -648,6 +664,8 @@ services:
   breadcrumb:
     class: Drupal\Core\Breadcrumb\BreadcrumbManager
     arguments: ['@module_handler']
+    tags:
+      - { name: service_collector, tag: breadcrumb_builder, call: addBuilder }
   token:
     class: Drupal\Core\Utility\Token
     arguments: ['@module_handler', '@cache.discovery', '@language_manager']
diff --git a/core/lib/Drupal/Core/Config/ConfigFactoryOverridePass.php b/core/lib/Drupal/Core/Config/ConfigFactoryOverridePass.php
deleted file mode 100644
index d270688..0000000
--- a/core/lib/Drupal/Core/Config/ConfigFactoryOverridePass.php
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Config\ConfigFactoryOverridePass.
- */
-
-namespace Drupal\Core\Config;
-
-use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Reference;
-
-/**
- * Adds services to the config factory service.
- *
- * @see \Drupal\Core\Config\ConfigFactory
- * @see \Drupal\Core\Config\ConfigFactoryOverrideInterface
- */
-class ConfigFactoryOverridePass implements CompilerPassInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function process(ContainerBuilder $container) {
-    $manager = $container->getDefinition('config.factory');
-    $services = array();
-    foreach ($container->findTaggedServiceIds('config.factory.override') as $id => $attributes) {
-      $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
-      $services[] = array('id' => $id, 'priority' => $priority);
-    }
-    usort($services, array($this, 'compareServicePriorities'));
-    foreach ($services as $service) {
-      $manager->addMethodCall('addOverride', array(new Reference($service['id'])));
-    }
-  }
-
-  /**
-   * Compares services by priority for ordering.
-   *
-   * @param array $a
-   *   Service to compare.
-   * @param array $b
-   *   Service to compare.
-   *
-   * @return int
-   *   Relative order of services to be used with usort. Higher priorities come
-   *   first.
-   */
-  private function compareServicePriorities($a, $b) {
-    if ($a['priority'] == $b['priority']) {
-      return 0;
-    }
-    return ($a['priority'] > $b['priority']) ? -1 : 1;
-  }
-
-}
diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php
index 94ad0cf..11c7781 100644
--- a/core/lib/Drupal/Core/CoreServiceProvider.php
+++ b/core/lib/Drupal/Core/CoreServiceProvider.php
@@ -9,26 +9,17 @@
 
 use Drupal\Core\Cache\CacheContextsPass;
 use Drupal\Core\Cache\ListCacheBinsPass;
-use Drupal\Core\Config\ConfigFactoryOverridePass;
 use Drupal\Core\DependencyInjection\ServiceProviderInterface;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass;
+use Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass;
-use Drupal\Core\DependencyInjection\Compiler\RegisterPathProcessorsPass;
-use Drupal\Core\DependencyInjection\Compiler\RegisterRouteProcessorsPass;
-use Drupal\Core\DependencyInjection\Compiler\RegisterRouteFiltersPass;
-use Drupal\Core\DependencyInjection\Compiler\RegisterRouteEnhancersPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterParamConvertersPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterServicesForDestructionPass;
-use Drupal\Core\DependencyInjection\Compiler\RegisterStringTranslatorsPass;
-use Drupal\Core\DependencyInjection\Compiler\RegisterBreadcrumbBuilderPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterAuthenticationPass;
-use Drupal\Core\DependencyInjection\Compiler\RegisterTwigExtensionsPass;
-use Drupal\Core\Http\HttpClientSubscriberPass;
 use Drupal\Core\Plugin\PluginManagerPass;
 use Drupal\Core\Site\Settings;
-use Drupal\Core\Theme\ThemeNegotiatorPass;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\DependencyInjection\Reference;
 use Symfony\Component\DependencyInjection\Definition;
@@ -62,39 +53,30 @@ public function register(ContainerBuilder $container) {
     // service definitions. This pass must come first so that later
     // list-building passes are operating on the post-alter services list.
     $container->addCompilerPass(new ModifyServiceDefinitionsPass());
-    $container->addCompilerPass(new RegisterRouteFiltersPass());
+
+    // Collect tagged handler services as method calls on consumer services.
+    $container->addCompilerPass(new TaggedHandlersPass());
+
     // Add a compiler pass for registering event subscribers.
     $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
+
     $container->addCompilerPass(new RegisterAccessChecksPass());
+
     // Add a compiler pass for upcasting route parameters.
     $container->addCompilerPass(new RegisterParamConvertersPass());
-    $container->addCompilerPass(new RegisterRouteEnhancersPass());
+
     // Add a compiler pass for registering services needing destruction.
     $container->addCompilerPass(new RegisterServicesForDestructionPass());
+
     // Add the compiler pass that will process the tagged services.
-    $container->addCompilerPass(new RegisterPathProcessorsPass());
-    $container->addCompilerPass(new RegisterRouteProcessorsPass());
     $container->addCompilerPass(new ListCacheBinsPass());
     $container->addCompilerPass(new CacheContextsPass());
-    // Add the compiler pass for appending string translators.
-    $container->addCompilerPass(new RegisterStringTranslatorsPass());
-    // Add the compiler pass that will process the tagged breadcrumb builder
-    // services.
-    $container->addCompilerPass(new RegisterBreadcrumbBuilderPass());
-    // Add the compiler pass that will process the tagged theme negotiator
-    // service.
-    $container->addCompilerPass(new ThemeNegotiatorPass());
-    // Add the compiler pass that will process the tagged config factory
-    // override services.
-    $container->addCompilerPass(new ConfigFactoryOverridePass());
+
     // Add the compiler pass that will process tagged authentication services.
     $container->addCompilerPass(new RegisterAuthenticationPass());
-    // Register Twig extensions.
-    $container->addCompilerPass(new RegisterTwigExtensionsPass());
+
     // Register plugin managers.
     $container->addCompilerPass(new PluginManagerPass());
-    // Register HTTP client subscribers.
-    $container->addCompilerPass(new HttpClientSubscriberPass());
   }
 
   /**
@@ -127,7 +109,11 @@ public static function registerTwig(ContainerBuilder $container) {
       ->addMethodCall('addExtension', array(new Definition('Drupal\Core\Template\TwigExtension')))
       // @todo Figure out what to do about debugging functions.
       // @see http://drupal.org/node/1804998
-      ->addMethodCall('addExtension', array(new Definition('Twig_Extension_Debug')));
+      ->addMethodCall('addExtension', array(new Definition('Twig_Extension_Debug')))
+      ->addTag('service_collector', array(
+        'tag' => 'twig.extension',
+        'call' => 'addExtension',
+      ));
   }
 
   /**
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterBreadcrumbBuilderPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterBreadcrumbBuilderPass.php
deleted file mode 100644
index 6cc73af..0000000
--- a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterBreadcrumbBuilderPass.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\DependencyInjection\Compiler\RegisterBreadcrumbBuilderPass.
- */
-
-namespace Drupal\Core\DependencyInjection\Compiler;
-
-use Symfony\Component\DependencyInjection\Reference;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
-
-/**
- * Adds services to the breadcrumb_builder service.
- */
-class RegisterBreadcrumbBuilderPass implements CompilerPassInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function process(ContainerBuilder $container) {
-    if (!$container->hasDefinition('breadcrumb')) {
-      return;
-    }
-    $manager = $container->getDefinition('breadcrumb');
-    if (is_subclass_of($manager->getClass(), 'Drupal\Core\Breadcrumb\ChainBreadcrumbBuilderInterface')) {
-      foreach ($container->findTaggedServiceIds('breadcrumb_builder') as $id => $attributes) {
-        $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
-        $manager->addMethodCall('addBuilder', array(new Reference($id), $priority));
-      }
-    }
-  }
-
-}
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterPathProcessorsPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterPathProcessorsPass.php
deleted file mode 100644
index 70de42d..0000000
--- a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterPathProcessorsPass.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\DependencyInjection\Compiler\RegisterPathProcessorsPass.
- */
-
-namespace Drupal\Core\DependencyInjection\Compiler;
-
-use Symfony\Component\DependencyInjection\Reference;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
-
-/**
- * Adds services to the 'path_processor_manager service.
- */
-class RegisterPathProcessorsPass implements CompilerPassInterface {
-
-  /**
-   * Adds services tagged 'path_processor_inbound' to the path processor manager.
-   *
-   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
-   *  The container to process.
-   */
-  public function process(ContainerBuilder $container) {
-    if (!$container->hasDefinition('path_processor_manager')) {
-      return;
-    }
-    $manager = $container->getDefinition('path_processor_manager');
-    // Add inbound path processors.
-    foreach ($container->findTaggedServiceIds('path_processor_inbound') as $id => $attributes) {
-      $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
-      $manager->addMethodCall('addInbound', array(new Reference($id), $priority));
-    }
-    // Add outbound path processors.
-    foreach ($container->findTaggedServiceIds('path_processor_outbound') as $id => $attributes) {
-      $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
-      $manager->addMethodCall('addOutbound', array(new Reference($id), $priority));
-    }
-  }
-}
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteEnhancersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteEnhancersPass.php
deleted file mode 100644
index 927c6dc..0000000
--- a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteEnhancersPass.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\DependencyInjection\Compiler\RegisterRouteEnhancersPass.
- */
-
-namespace Drupal\Core\DependencyInjection\Compiler;
-
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
-use Symfony\Component\DependencyInjection\Reference;
-
-/**
- * Registers route enhancer services with the router.
- */
-class RegisterRouteEnhancersPass implements CompilerPassInterface {
-
-  /**
-   * Adds services tagged with "route_enhancer" to the router.
-   *
-   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
-   *   The container to process.
-   */
-  public function process(ContainerBuilder $container) {
-    if (!$container->hasDefinition('router.dynamic')) {
-      return;
-    }
-
-    $router = $container->getDefinition('router.dynamic');
-    foreach ($container->findTaggedServiceIds('route_enhancer') as $id => $attributes) {
-      $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
-      $router->addMethodCall('addRouteEnhancer', array(new Reference($id), $priority));
-    }
-  }
-}
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteFiltersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteFiltersPass.php
deleted file mode 100644
index da47fca..0000000
--- a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteFiltersPass.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\DependencyInjection\Compiler\RegisterRouteFiltersPass.
- */
-
-namespace Drupal\Core\DependencyInjection\Compiler;
-
-use Symfony\Component\DependencyInjection\Reference;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
-
-/**
- * Adds services tagged 'router.matcher' to the matcher service.
- */
-class RegisterRouteFiltersPass implements CompilerPassInterface {
-
-  /**
-   * Adds services tagged 'router.matcher' to the matcher service.
-   *
-   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
-   *   The container to process.
-   */
-  public function process(ContainerBuilder $container) {
-    if (!$container->hasDefinition('router.matcher')) {
-      return;
-    }
-    $matcher = $container->getDefinition('router.matcher');
-    foreach ($container->findTaggedServiceIds('route_filter') as $id => $attributes) {
-      $matcher->addMethodCall('addRouteFilter', array(new Reference($id)));
-    }
-  }
-}
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteProcessorsPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteProcessorsPass.php
deleted file mode 100644
index dd95869..0000000
--- a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteProcessorsPass.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\DependencyInjection\Compiler\RegisterRouteProcessorsPass.
- */
-
-namespace Drupal\Core\DependencyInjection\Compiler;
-
-use Symfony\Component\DependencyInjection\Reference;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
-
-/**
- * Adds services to the route_processor_manager service.
- */
-class RegisterRouteProcessorsPass implements CompilerPassInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function process(ContainerBuilder $container) {
-    if (!$container->hasDefinition('route_processor_manager')) {
-      return;
-    }
-    $manager = $container->getDefinition('route_processor_manager');
-    // Add outbound route processors.
-    foreach ($container->findTaggedServiceIds('route_processor_outbound') as $id => $attributes) {
-      $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
-      $manager->addMethodCall('addOutbound', array(new Reference($id), $priority));
-    }
-  }
-
-}
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterStringTranslatorsPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterStringTranslatorsPass.php
deleted file mode 100644
index 39a0764..0000000
--- a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterStringTranslatorsPass.php
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\DependencyInjection\Compiler\RegisterStringTranslatorsPass.
- */
-
-namespace Drupal\Core\DependencyInjection\Compiler;
-
-use Symfony\Component\DependencyInjection\Reference;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
-
-/**
- * Adds services tagged 'string_translator' to the string_translation service.
- */
-class RegisterStringTranslatorsPass implements CompilerPassInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function process(ContainerBuilder $container) {
-    if (!$container->hasDefinition('string_translation')) {
-      return;
-    }
-    $access_manager = $container->getDefinition('string_translation');
-    foreach ($container->findTaggedServiceIds('string_translator') as $id => $attributes) {
-      $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
-      $access_manager->addMethodCall('addTranslator', array(new Reference($id), $priority));
-    }
-  }
-
-}
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterTwigExtensionsPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterTwigExtensionsPass.php
deleted file mode 100644
index 3df9b87..0000000
--- a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterTwigExtensionsPass.php
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\DependencyInjection\Compiler\RegisterTwigExtensionsPass.
- */
-
-namespace Drupal\Core\DependencyInjection\Compiler;
-
-use Symfony\Component\DependencyInjection\Reference;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
-
-/**
- * Register additional Twig extensions to the Twig service container.
- */
-class RegisterTwigExtensionsPass implements CompilerPassInterface {
-
-  /**
-   * Adds services tagged 'twig.extension' to the Twig service container.
-   *
-   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
-   *   The container to process.
-   */
-  public function process(ContainerBuilder $container) {
-    if (!$container->hasDefinition('twig')) {
-      return;
-    }
-
-    $definition = $container->getDefinition('twig');
-
-    foreach ($container->findTaggedServiceIds('twig.extension') as $id => $attributes) {
-      // We must assume that the class value has been correcly filled,
-      // even if the service is created by a factory.
-      $class = $container->getDefinition($id)->getClass();
-
-      $refClass = new \ReflectionClass($class);
-      $interface = 'Twig_ExtensionInterface';
-      if (!$refClass->implementsInterface($interface)) {
-        throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
-      }
-      $definition->addMethodCall('addExtension', array(new Reference($id)));
-    }
-  }
-
-}
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php
new file mode 100644
index 0000000..9ebdaaa
--- /dev/null
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass.
+ */
+
+namespace Drupal\Core\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Exception\LogicException;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Collects services to add/inject them into a consumer service.
+ *
+ * This mechanism allows a service to get multiple processor services injected,
+ * in order to establish an extensible architecture.
+ *
+ * It differs from the factory pattern in that processors are not lazily
+ * instantiated on demand; the consuming service receives instances of all
+ * registered processors when it is instantiated. Unlike a factory service, the
+ * consuming service is not ContainerAware.
+ *
+ * It differs from plugins in that all processors are explicitly registered by
+ * service providers (driven by declarative configuration in code); the mere
+ * availability of a processor (cf. plugin discovery) does not imply that a
+ * processor ought to be registered and used.
+ *
+ * It differs from regular service definition arguments (constructor injection)
+ * in that a consuming service MAY allow further processors to be added
+ * dynamically at runtime. This is why the called method (optionally) receives
+ * the priority of a processor as second argument.
+ *
+ * @see \Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass::process()
+ */
+class TaggedHandlersPass implements CompilerPassInterface {
+
+  /**
+   * {@inheritdoc}
+   *
+   * Finds services tagged with 'service_collector', then finds all
+   * corresponding tagged services and adds a method call for each to the
+   * consuming/collecting service definition.
+   *
+   * Supported 'service_collector' tag attributes:
+   * - tag: The tag name used by handler services to collect. Defaults to the
+   *   service ID of the consumer.
+   * - call: The method name to call on the consumer service. Defaults to
+   *   'addHandler'. The called method receives two arguments:
+   *   - The handler instance as first argument.
+   *   - Optionally the handler's priority as second argument, if the method
+   *     accepts a second parameter and its name is "priority". In any case, all
+   *     handlers registered at compile time are sorted already.
+   *
+   * Example (YAML):
+   * @code
+   * tags:
+   *   - { name: service_collector, tag: breadcrumb_builder, call: addBuilder }
+   * @endcode
+   *
+   * Supported handler tag attributes:
+   * - priority: An integer denoting the priority of the handler. Defaults to 0.
+   *
+   * Example (YAML):
+   * @code
+   * tags:
+   *   - { name: breadcrumb_builder, priority: 100 }
+   * @endcode
+   *
+   * @throws \Symfony\Component\DependencyInjection\Exception\LogicException
+   *   If the method of a consumer service to be called does not type-hint an
+   *   interface.
+   * @throws \Symfony\Component\DependencyInjection\Exception\LogicException
+   *   If a tagged handler does not implement the required interface.
+   */
+  public function process(ContainerBuilder $container) {
+    foreach ($container->findTaggedServiceIds('service_collector') as $consumer_id => $passes) {
+      foreach ($passes as $pass) {
+        $tag = isset($pass['tag']) ? $pass['tag'] : $consumer_id;
+        $method_name = isset($pass['call']) ? $pass['call'] : 'addHandler';
+
+        // Determine parameters.
+        $consumer = $container->getDefinition($consumer_id);
+        $method = new \ReflectionMethod($consumer->getClass(), $method_name);
+        $params = $method->getParameters();
+        $interface = $params[0]->getClass();
+        $accepts_priority = isset($params[1]) && $params[1]->getName() === 'priority';
+
+        if (!isset($interface)) {
+          if ($container->getParameter('kernel.environment') === 'prod') {
+            continue;
+          }
+          throw new LogicException(vsprintf("Service consumer '%s' class method %s::%s() has to type-hint an interface.", array(
+            $consumer_id,
+            $consumer->getClass(),
+            $method_name,
+          )));
+        }
+        $interface = $interface->getName();
+
+        // Find all tagged handlers.
+        $handlers = array();
+        foreach ($container->findTaggedServiceIds($tag) as $id => $attributes) {
+          // Validate the interface.
+          $handler = $container->getDefinition($id);
+          if (!is_subclass_of($handler->getClass(), $interface)) {
+            if ($container->getParameter('kernel.environment') === 'prod') {
+              continue;
+            }
+            throw new LogicException("Service '$id' for consumer '$consumer_id' does not implement $interface.");
+          }
+          $handlers[$id] = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
+        }
+        if (empty($handlers)) {
+          continue;
+        }
+        // Sort all handlers by priority.
+        arsort($handlers, SORT_NUMERIC);
+
+        // Add a method call for each handler to the consumer service
+        // definition.
+        foreach ($handlers as $id => $priority) {
+          if ($accepts_priority) {
+            $consumer->addMethodCall($method_name, array(new Reference($id), $priority));
+          }
+          else {
+            $consumer->addMethodCall($method_name, array(new Reference($id)));
+          }
+        }
+      }
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Http/HttpClientSubscriberPass.php b/core/lib/Drupal/Core/Http/HttpClientSubscriberPass.php
deleted file mode 100644
index 7cba46a..0000000
--- a/core/lib/Drupal/Core/Http/HttpClientSubscriberPass.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Http\HttpClientSubscriberPass.
- */
-
-namespace Drupal\Core\Http;
-
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
-use Symfony\Component\DependencyInjection\Reference;
-
-/**
- * Registers 'http_client_subscriber' tagged services as http client subscribers.
- */
-class HttpClientSubscriberPass implements CompilerPassInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function process(ContainerBuilder $container) {
-    $http_client = $container->getDefinition('http_client');
-
-    foreach (array_keys($container->findTaggedServiceIds('http_client_subscriber')) as $id) {
-      $http_client->addMethodCall('attach', array(new Reference($id)));
-    }
-  }
-
-}
-
diff --git a/core/lib/Drupal/Core/PathProcessor/PathProcessorManager.php b/core/lib/Drupal/Core/PathProcessor/PathProcessorManager.php
index db8b799..3c8b9be 100644
--- a/core/lib/Drupal/Core/PathProcessor/PathProcessorManager.php
+++ b/core/lib/Drupal/Core/PathProcessor/PathProcessorManager.php
@@ -57,7 +57,6 @@ class PathProcessorManager implements InboundPathProcessorInterface, OutboundPat
    *
    * @param \Drupal\Core\PathProcessor\InboundPathProcessorInterface $processor
    *   The processor object to add.
-   *
    * @param int $priority
    *   The priority of the processor being added.
    */
@@ -97,7 +96,6 @@ protected function getInbound() {
    *
    * @param \Drupal\Core\PathProcessor\OutboundPathProcessorInterface $processor
    *   The processor object to add.
-   *
    * @param int $priority
    *   The priority of the processor being added.
    */
diff --git a/core/lib/Drupal/Core/RouteProcessor/RouteProcessorManager.php b/core/lib/Drupal/Core/RouteProcessor/RouteProcessorManager.php
index 43071be..3dc8864 100644
--- a/core/lib/Drupal/Core/RouteProcessor/RouteProcessorManager.php
+++ b/core/lib/Drupal/Core/RouteProcessor/RouteProcessorManager.php
@@ -39,7 +39,6 @@ class RouteProcessorManager implements OutboundRouteProcessorInterface {
    *
    * @param \Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface $processor
    *   The processor object to add.
-   *
    * @param int $priority
    *   The priority of the processor being added.
    */
diff --git a/core/lib/Drupal/Core/Theme/ThemeNegotiatorPass.php b/core/lib/Drupal/Core/Theme/ThemeNegotiatorPass.php
deleted file mode 100644
index 7f14692..0000000
--- a/core/lib/Drupal/Core/Theme/ThemeNegotiatorPass.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Theme\ThemeNegotiatorPass.
- */
-
-namespace Drupal\Core\Theme;
-
-use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Reference;
-
-/**
- * Adds services to the theme negotiator service.
- *
- * @see \Drupal\Core\Theme\ThemeNegotiator
- * @see \Drupal\Core\Theme\ThemeNegotiatorInterfa
- */
-class ThemeNegotiatorPass implements CompilerPassInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function process(ContainerBuilder $container) {
-    if (!$container->hasDefinition('theme.negotiator')) {
-      return;
-    }
-    $manager = $container->getDefinition('theme.negotiator');
-    foreach ($container->findTaggedServiceIds('theme_negotiator') as $id => $attributes) {
-      $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
-      $manager->addMethodCall('addNegotiator', array(new Reference($id), $priority));
-    }
-  }
-
-}
diff --git a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php
new file mode 100644
index 0000000..528c45e
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php
@@ -0,0 +1,255 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\DependencyInjection\Compiler\TaggedHandlersPassTest.
+ */
+
+namespace Drupal\Tests\Core\DependencyInjection\Compiler;
+
+use Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Tests the tagged handler compiler pass.
+ *
+ * @group Drupal
+ * @group DependencyInjection
+ *
+ * @coversDefaultClass \Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass
+ */
+class TaggedHandlersPassTest extends UnitTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Tests \Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass',
+      'description' => '',
+      'group' => 'Dependency injection',
+    );
+  }
+
+  protected function buildContainer($environment = 'dev') {
+    $container = new ContainerBuilder();
+    $container->setParameter('kernel.environment', $environment);
+    return $container;
+  }
+
+  /**
+   * Tests without any consumers.
+   *
+   * @covers ::process
+   */
+  public function testProcessNoConsumers() {
+    $container = $this->buildContainer();
+    $container
+      ->register('consumer_id', __NAMESPACE__ . '\ValidConsumer');
+
+    $handler_pass = new TaggedHandlersPass();
+    $handler_pass->process($container);
+
+    $this->assertCount(1, $container->getDefinitions());
+    $this->assertFalse($container->getDefinition('consumer_id')->hasMethodCall('addHandler'));
+  }
+
+  /**
+   * Tests consumer with missing interface in non-production environment.
+   *
+   * @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException
+   * @expectedExceptionMessage Service consumer 'consumer_id' class method Drupal\Tests\Core\DependencyInjection\Compiler\InvalidConsumer::addHandler() has to type-hint an interface.
+   * @covers ::process
+   */
+  public function testProcessMissingInterface() {
+    $container = $this->buildContainer();
+    $container
+      ->register('consumer_id', __NAMESPACE__ . '\InvalidConsumer')
+      ->addTag('service_collector');
+
+    $handler_pass = new TaggedHandlersPass();
+    $handler_pass->process($container);
+  }
+
+  /**
+   * Tests consumer with missing interface in production environment.
+   *
+   * @covers ::process
+   */
+  public function testProcessMissingInterfaceProd() {
+    $container = $this->buildContainer('prod');
+    $container
+      ->register('consumer_id', __NAMESPACE__ . '\InvalidConsumer')
+      ->addTag('service_collector');
+
+    $handler_pass = new TaggedHandlersPass();
+    $handler_pass->process($container);
+
+    $this->assertCount(1, $container->getDefinitions());
+    $this->assertFalse($container->getDefinition('consumer_id')->hasMethodCall('addHandler'));
+  }
+
+  /**
+   * Tests one consumer and two handlers.
+   *
+   * @covers ::process
+   */
+  public function testProcess() {
+    $container = $this->buildContainer();
+    $container
+      ->register('consumer_id', __NAMESPACE__ . '\ValidConsumer')
+      ->addTag('service_collector');
+
+    $container
+      ->register('handler1', __NAMESPACE__ . '\ValidHandler')
+      ->addTag('consumer_id');
+    $container
+      ->register('handler2', __NAMESPACE__ . '\ValidHandler')
+      ->addTag('consumer_id');
+
+    $handler_pass = new TaggedHandlersPass();
+    $handler_pass->process($container);
+
+    $method_calls = $container->getDefinition('consumer_id')->getMethodCalls();
+    $this->assertCount(2, $method_calls);
+  }
+
+  /**
+   * Tests handler priority sorting.
+   *
+   * @covers ::process
+   */
+  public function testProcessPriority() {
+    $container = $this->buildContainer();
+    $container
+      ->register('consumer_id', __NAMESPACE__ . '\ValidConsumer')
+      ->addTag('service_collector');
+
+    $container
+      ->register('handler1', __NAMESPACE__ . '\ValidHandler')
+      ->addTag('consumer_id');
+    $container
+      ->register('handler2', __NAMESPACE__ . '\ValidHandler')
+      ->addTag('consumer_id', array(
+        'priority' => 10,
+      ));
+
+    $handler_pass = new TaggedHandlersPass();
+    $handler_pass->process($container);
+
+    $method_calls = $container->getDefinition('consumer_id')->getMethodCalls();
+    $this->assertCount(2, $method_calls);
+    $this->assertEquals(new Reference('handler2'), $method_calls[0][1][0]);
+    $this->assertEquals(10, $method_calls[0][1][1]);
+    $this->assertEquals(new Reference('handler1'), $method_calls[1][1][0]);
+    $this->assertEquals(0, $method_calls[1][1][1]);
+  }
+
+  /**
+   * Tests consumer method without priority parameter.
+   *
+   * @covers ::process
+   */
+  public function testProcessNoPriorityParam() {
+    $container = $this->buildContainer();
+    $container
+      ->register('consumer_id', __NAMESPACE__ . '\ValidConsumer')
+      ->addTag('service_collector', array(
+        'call' => 'addNoPriority',
+      ));
+
+    $container
+      ->register('handler1', __NAMESPACE__ . '\ValidHandler')
+      ->addTag('consumer_id');
+    $container
+      ->register('handler2', __NAMESPACE__ . '\ValidHandler')
+      ->addTag('consumer_id', array(
+        'priority' => 10,
+      ));
+
+    $handler_pass = new TaggedHandlersPass();
+    $handler_pass->process($container);
+
+    $method_calls = $container->getDefinition('consumer_id')->getMethodCalls();
+    $this->assertCount(2, $method_calls);
+    $this->assertEquals(new Reference('handler2'), $method_calls[0][1][0]);
+    $this->assertCount(1, $method_calls[0][1]);
+    $this->assertEquals(new Reference('handler1'), $method_calls[1][1][0]);
+    $this->assertCount(1, $method_calls[0][1]);
+  }
+
+  /**
+   * Tests interface validation in non-production environment.
+   *
+   * @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException
+   * @covers ::process
+   */
+  public function testProcessInterfaceMismatch() {
+    $container = $this->buildContainer();
+
+    $container
+      ->register('consumer_id', __NAMESPACE__ . '\ValidConsumer')
+      ->addTag('service_collector');
+    $container
+      ->register('handler1', __NAMESPACE__ . '\InvalidHandler')
+      ->addTag('consumer_id');
+    $container
+      ->register('handler2', __NAMESPACE__ . '\ValidHandler')
+      ->addTag('consumer_id', array(
+        'priority' => 10,
+      ));
+
+    $handler_pass = new TaggedHandlersPass();
+    $handler_pass->process($container);
+  }
+
+  /**
+   * Tests interface validation in production environment.
+   *
+   * @covers ::process
+   */
+  public function testProcessInterfaceMismatchProd() {
+    $container = $this->buildContainer('prod');
+
+    $container
+      ->register('consumer_id', __NAMESPACE__ . '\ValidConsumer')
+      ->addTag('service_collector');
+    $container
+      ->register('handler1', __NAMESPACE__ . '\InvalidHandler')
+      ->addTag('consumer_id');
+    $container
+      ->register('handler2', __NAMESPACE__ . '\ValidHandler')
+      ->addTag('consumer_id', array(
+        'priority' => 10,
+      ));
+
+    $handler_pass = new TaggedHandlersPass();
+    $handler_pass->process($container);
+
+    $method_calls = $container->getDefinition('consumer_id')->getMethodCalls();
+    $this->assertCount(1, $method_calls);
+    $this->assertEquals(new Reference('handler2'), $method_calls[0][1][0]);
+    $this->assertEquals(10, $method_calls[0][1][1]);
+  }
+
+}
+
+interface HandlerInterface {
+}
+class ValidConsumer {
+  public function addHandler(HandlerInterface $instance, $priority = 0) {
+  }
+  public function addNoPriority(HandlerInterface $instance) {
+  }
+}
+class InvalidConsumer {
+  public function addHandler($instance, $priority = 0) {
+  }
+}
+class ValidHandler implements HandlerInterface {
+}
+class InvalidHandler {
+}
+
