From 00c06fcfedb19f1e1f86016480b29c06330972c4 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Thu, 26 Sep 2013 10:32:39 +0200
Subject: [PATCH] Issue #2070651 by pwolanin, dawehner, larowlan, tim.plunkett,
 ParisLiakos: Introduce a path-based breadcrumb builder, remove the one that's
 based on {menu_links()}.

---
 core/core.services.yml                        |   1 +
 core/includes/menu.inc                        |  73 +-----
 .../Core/Breadcrumb/BreadcrumbManager.php     |  36 ++-
 .../Core/Controller/ExceptionController.php   |   4 +-
 core/modules/book/book.services.yml           |   5 +
 .../lib/Drupal/book/BookBreadcrumbBuilder.php | 108 ++++++++
 .../field_ui/Routing/RouteSubscriber.php      |  15 +-
 core/modules/filter/filter.routing.yml        |   1 +
 .../PathProcessorImageStyles.php              |  13 +-
 core/modules/menu/menu.routing.yml            |   1 +
 .../menu_link/MenuLinkBreadcrumbBuilder.php   |  26 --
 .../menu_link/MenuLinkFormController.php      |  11 -
 core/modules/menu_link/menu_link.module       |  18 ++
 core/modules/menu_link/menu_link.services.yml |   5 -
 .../Drupal/node/Controller/NodeController.php |   7 -
 .../lib/Drupal/node/Controller/NodeView.php   |  35 +++
 core/modules/node/node.module                 |   4 -
 core/modules/node/node.routing.yml            |   4 +-
 .../system/PathBasedBreadcrumbBuilder.php     | 239 ++++++++++++++++++
 .../system/Tests/Menu/BreadcrumbTest.php      | 119 +--------
 .../Drupal/system/Tests/Menu/MenuTestBase.php |   5 +-
 .../system/Tests/Menu/MenuTranslateTest.php   |   6 +-
 .../Drupal/system/Tests/Menu/TrailTest.php    |  63 -----
 .../system/Tests/Menu/TreeAccessTest.php      |   7 +
 core/modules/system/system.api.php            |  70 ++---
 core/modules/system/system.routing.yml        |  13 +
 core/modules/system/system.services.yml       |   5 +
 core/modules/taxonomy/taxonomy.routing.yml    |   1 +
 28 files changed, 543 insertions(+), 352 deletions(-)
 create mode 100644 core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php
 delete mode 100644 core/modules/menu_link/lib/Drupal/menu_link/MenuLinkBreadcrumbBuilder.php
 delete mode 100644 core/modules/menu_link/menu_link.services.yml
 create mode 100644 core/modules/node/lib/Drupal/node/Controller/NodeView.php
 create mode 100644 core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php

diff --git a/core/core.services.yml b/core/core.services.yml
index c369394ce7db..79f1fc1bc999 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -531,6 +531,7 @@ services:
     arguments: ['@image.toolkit']
   breadcrumb:
     class: Drupal\Core\Breadcrumb\BreadcrumbManager
+    arguments: ['@module_handler']
   token:
     class: Drupal\Core\Utility\Token
     arguments: ['@module_handler']
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 46ed01d1e246..3ba3b5ddf2dd 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -146,7 +146,7 @@
  */
 
 /**
- * Menu type -- A "normal" menu item that's shown in menu and breadcrumbs.
+ * Menu type -- A "normal" menu item that's shown in menus.
  *
  * Normal menu items show up in the menu tree and can be moved/hidden by
  * the administrator. Use this for most menu items. It is the default value if
@@ -158,7 +158,7 @@
  * Menu type -- A hidden, internal callback, typically used for API calls.
  *
  * Callbacks simply register a path so that the correct function is fired
- * when the URL is accessed. They do not appear in menus or breadcrumbs.
+ * when the URL is accessed. They do not appear in menus.
  */
 const MENU_CALLBACK = 0x0000;
 
@@ -200,7 +200,7 @@
 /**
  * Menu type -- A task specific to the parent, which is never rendered.
  *
- * Sibling local tasks are not rendered themselves, but affect the breadcrumb
+ * Sibling local tasks are not rendered themselves, but affect the active
  * trail and need their sibling tasks rendered as tabs.
  */
 define('MENU_SIBLING_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_IS_LOCAL_ACTION | MENU_VISIBLE_IN_BREADCRUMB);
@@ -437,7 +437,7 @@ function menu_unserialize($data, $map) {
  * @param $router_item
  *   The router item. Usually a router entry from menu_get_item() is either
  *   modified or set to a different path. This allows the navigation block,
- *   the page title, the breadcrumb, and the page help to be modified in one
+ *   the page title, the active trail, and the page help to be modified in one
  *   call.
  */
 function menu_set_item($path, $router_item) {
@@ -898,7 +898,6 @@ function _menu_link_translate(&$item, $translate = FALSE) {
       // current path, and we can take over the argument map for a link like
       // 'foo/%/bar'. This inheritance is only valid for breadcrumb links.
       // @see _menu_tree_check_access()
-      // @see menu_get_active_breadcrumb()
       elseif ($translate && ($current_router_item = menu_get_item())) {
         // If $translate is TRUE, then this link is in the active trail.
         // Only translate paths within the current path.
@@ -1281,7 +1280,7 @@ function menu_tree_get_path($menu_name) {
  * @param $only_active_trail
  *   (optional) Whether to only return the links in the active trail (TRUE)
  *   instead of all links on every level of the menu link tree (FALSE). Defaults
- *   to FALSE. Internally used for breadcrumbs only.
+ *   to FALSE.
  *
  * @return
  *   An array of menu links, in the order they should be rendered. The array
@@ -1328,7 +1327,7 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail =
     // template_preprocess_page(). So in order to not build a giant menu tree
     // that needs to be checked for access on all levels, we simply check
     // whether we have the menu already in cache, or otherwise, build a minimum
-    // tree containing the breadcrumb/active trail only.
+    // tree containing the active trail only.
     // @see menu_set_active_trail()
     if (!isset($tree[$cid]) && $only_active_trail) {
       $cid .= ':trail';
@@ -1430,8 +1429,7 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail =
  *   - active_trail: An array of mlids, representing the coordinates of the
  *     currently active menu link.
  *   - only_active_trail: Whether to only return links that are in the active
- *     trail. This option is ignored, if 'expanded' is non-empty. Internally
- *     used for breadcrumbs.
+ *     trail. This option is ignored, if 'expanded' is non-empty.
  *   - min_depth: The minimum depth of menu links in the resulting tree.
  *     Defaults to 1, which is the default to build a whole tree for a menu
  *     (excluding menu container itself).
@@ -1641,7 +1639,7 @@ function _menu_tree_data(&$links, $parents, $depth) {
   $tree = array();
   while ($item = array_pop($links)) {
     // We need to determine if we're on the path to root so we can later build
-    // the correct active trail and breadcrumb.
+    // the correct active trail.
     $item['in_active_trail'] = in_array($item['mlid'], $parents);
     // Add the current link to the tree.
     $tree[$item['mlid']] = array(
@@ -2441,9 +2439,7 @@ function menu_set_active_item($path) {
  *
  * Any trail set by this function will only be used for functionality that calls
  * menu_get_active_trail(). Drupal core only uses trails set here for
- * breadcrumbs and the page title and not for menu trees or page content.
- * Additionally, breadcrumbs set by drupal_set_breadcrumb() will override any
- * trail set here.
+ * the page title and not for menu trees or page content.
  *
  * To affect the trail used by menu trees, use menu_tree_set_path(). To affect
  * the page content, use menu_set_active_item() instead.
@@ -2639,57 +2635,6 @@ function menu_get_active_trail() {
   return menu_set_active_trail();
 }
 
-/**
- * Gets the breadcrumb for the current page, as determined by the active trail.
- *
- * @see menu_set_active_trail()
- */
-function menu_get_active_breadcrumb() {
-  $breadcrumb = array();
-
-  // No breadcrumb for the front page.
-  if (drupal_is_front_page()) {
-    return $breadcrumb;
-  }
-
-  $item = menu_get_item();
-  if (!empty($item['access'])) {
-    $active_trail = menu_get_active_trail();
-
-    // Allow modules to alter the breadcrumb, if possible, as that is much
-    // faster than rebuilding an entirely new active trail.
-    drupal_alter('menu_breadcrumb', $active_trail, $item);
-
-    // Don't show a link to the current page in the breadcrumb trail.
-    $end = end($active_trail);
-    if (\Drupal::request()->attributes->get('_system_path') == $end['href']) {
-      array_pop($active_trail);
-    }
-
-    // Remove the tab root (parent) if the current path links to its parent.
-    // Normally, the tab root link is included in the breadcrumb, as soon as we
-    // are on a local task or any other child link. However, if we are on a
-    // default local task (e.g., node/%/view), then we do not want the tab root
-    // link (e.g., node/%) to appear, as it would be identical to the current
-    // page. Since this behavior also needs to work recursively (i.e., on
-    // default local tasks of default local tasks), and since the last non-task
-    // link in the trail is used as page title (see menu_get_active_title()),
-    // this condition cannot be cleanly integrated into menu_get_active_trail().
-    // menu_get_active_trail() already skips all links that link to their parent
-    // (commonly MENU_DEFAULT_LOCAL_TASK). In order to also hide the parent link
-    // itself, we always remove the last link in the trail, if the current
-    // router item links to its parent.
-    if (($item['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT) {
-      array_pop($active_trail);
-    }
-
-    foreach ($active_trail as $parent) {
-      $breadcrumb[] = l($parent['title'], $parent['href'], $parent['localized_options']);
-    }
-  }
-  return $breadcrumb;
-}
-
 /**
  * Gets the title of the current page, as determined by the active trail.
  */
diff --git a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php
index 5396e9ee9acb..d38449cefc85 100644
--- a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php
+++ b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Breadcrumb;
 
+use Drupal\Core\Extension\ModuleHandlerInterface;
+
 /**
  * Provides a breadcrumb manager.
  *
@@ -15,6 +17,13 @@
  */
 class BreadcrumbManager implements BreadcrumbBuilderInterface {
 
+  /**
+   * The module handler to invoke the alter hook.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
   /**
    * Holds arrays of breadcrumb builders, keyed by priority.
    *
@@ -31,6 +40,16 @@ class BreadcrumbManager implements BreadcrumbBuilderInterface {
    */
   protected $sortedBuilders;
 
+  /**
+   * Constructs a \Drupal\Core\Breadcrumb\BreadcrumbManager object.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler) {
+    $this->moduleHandler = $module_handler;
+  }
+
   /**
    * Adds another breadcrumb builder.
    *
@@ -49,25 +68,30 @@ public function addBuilder(BreadcrumbBuilderInterface $builder, $priority) {
    * {@inheritdoc}
    */
   public function build(array $attributes) {
+    $breadcrumb = array();
+    $context = array('builder' => NULL);
     // Call the build method of registered breadcrumb builders,
     // until one of them returns an array.
     foreach ($this->getSortedBuilders() as $builder) {
-      $breadcrumb = $builder->build($attributes);
-      if (!isset($breadcrumb)) {
+      $build = $builder->build($attributes);
+      if (!isset($build)) {
         // The builder returned NULL, so we continue with the other builders.
         continue;
       }
-      elseif (is_array($breadcrumb)) {
+      elseif (is_array($build)) {
         // The builder returned an array of breadcrumb links.
-        return $breadcrumb;
+        $breadcrumb = $build;
+        $context['builder'] = $builder;
+        break;
       }
       else {
         throw new \UnexpectedValueException(format_string('Invalid breadcrumb returned by !class::build().', array('!class' => get_class($builder))));
       }
     }
-
+    // Allow modules to alter the breadcrumb.
+    $this->moduleHandler->alter('system_breadcrumb', $breadcrumb, $attributes, $context);
     // Fall back to an empty breadcrumb.
-    return array();
+    return $breadcrumb;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Controller/ExceptionController.php b/core/lib/Drupal/Core/Controller/ExceptionController.php
index b03cc8e54e33..2d8adf012f2a 100644
--- a/core/lib/Drupal/Core/Controller/ExceptionController.php
+++ b/core/lib/Drupal/Core/Controller/ExceptionController.php
@@ -91,7 +91,7 @@ public function on403Html(FlattenException $exception, Request $request) {
         $request->query->set('destination', $system_path);
       }
 
-      $subrequest = Request::create('/' . $path, 'get', array('destination' => $system_path), $request->cookies->all(), array(), $request->server->all());
+      $subrequest = Request::create($request->getBaseUrl() . '/' . $path, 'get', array('destination' => $system_path), $request->cookies->all(), array(), $request->server->all());
 
       // The active trail is being statically cached from the parent request to
       // the subrequest, like any other static.  Unfortunately that means the
@@ -164,7 +164,7 @@ public function on404Html(FlattenException $exception, Request $request) {
       //   that and sub-call the kernel rather than using meah().
       // @todo The create() method expects a slash-prefixed path, but we store a
       //   normal system path in the site_404 variable.
-      $subrequest = Request::create('/' . $path, 'get', array(), $request->cookies->all(), array(), $request->server->all());
+      $subrequest = Request::create($request->getBaseUrl() . '/' . $path, 'get', array('destination' => $system_path), $request->cookies->all(), array(), $request->server->all());
 
       // The active trail is being statically cached from the parent request to
       // the subrequest, like any other static.  Unfortunately that means the
diff --git a/core/modules/book/book.services.yml b/core/modules/book/book.services.yml
index 2f1097a0666a..f7ff964c4d4b 100644
--- a/core/modules/book/book.services.yml
+++ b/core/modules/book/book.services.yml
@@ -1,4 +1,9 @@
 services:
+  book.breadcrumb:
+    class: Drupal\book\BookBreadcrumbBuilder
+    arguments: ['@entity.manager', '@string_translation', '@link_generator', '@access_manager']
+    tags:
+      - { name: breadcrumb_builder, priority: 701 }
   book.manager:
     class: Drupal\book\BookManager
     arguments: ['@database', '@entity.manager', '@string_translation', '@config.factory']
diff --git a/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php b/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php
new file mode 100644
index 000000000000..2fef9276bca6
--- /dev/null
+++ b/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\book\BookBreadcrumbBuilder.
+ */
+
+namespace Drupal\book;
+
+use Drupal\Core\Access\AccessManager;
+use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
+use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\Core\Utility\LinkGeneratorInterface;
+use Drupal\node\NodeInterface;
+
+/**
+ * Provides a breadcrumb builder for nodes in a book.
+ */
+class BookBreadcrumbBuilder implements BreadcrumbBuilderInterface {
+
+  /**
+   * The menu link storage controller.
+   *
+   * @var \Drupal\menu_link\MenuLinkStorageControllerInterface
+   */
+  protected $menuLinkStorage;
+
+  /**
+   * The translation manager service.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface;
+   */
+  protected $translation;
+
+  /**
+   * The link generator service.
+   *
+   * @var \Drupal\Core\Utility\LinkGeneratorInterface
+   */
+  protected $linkGenerator;
+
+  /**
+   * The access manager.
+   *
+   * @var \Drupal\Core\Access\AccessManager
+   */
+  protected $accessManager;
+
+  /**
+   * Constructs the BookBreadcrumbBuilder.
+   *
+   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   *   The entity manager service.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
+   *   The translation manager service.
+   * @param \Drupal\Core\Utility\LinkGeneratorInterface $link_generator
+   *   The link generator.
+   * @param \Drupal\Core\Access\AccessManager $access_manager
+   *   The access manager.
+   */
+  public function __construct(EntityManager $entity_manager, TranslationInterface $translation, LinkGeneratorInterface $link_generator, AccessManager $access_manager) {
+    $this->menuLinkStorage = $entity_manager->getStorageController('menu_link');
+    $this->translation = $translation;
+    $this->linkGenerator = $link_generator;
+    $this->accessManager = $access_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build(array $attributes) {
+    if (!empty($attributes['node']) && $attributes['node'] instanceof NodeInterface && !empty($attributes['node']->book)) {
+      $mlids = array();
+      $links = array($this->linkGenerator->generate($this->t('Home'), '<front>'));
+      $book = $attributes['node']->book;
+      $depth = 1;
+      // We skip the current node.
+      while (!empty($book['p' . ($depth + 1)])) {
+        $mlids[] = $book['p' . $depth];
+        $depth++;
+      }
+      $menu_links = $this->menuLinkStorage->loadMultiple($mlids);
+      if (count($menu_links) > 0) {
+        $depth = 1;
+        while (!empty($book['p' . ($depth + 1)])) {
+          if (!empty($menu_links[$book['p' . $depth]]) && ($menu_link = $menu_links[$book['p' . $depth]])) {
+            if ($this->accessManager->checkNamedRoute($menu_link->route_name, $menu_link->route_parameters)) {
+              $links[] = $this->linkGenerator->generate($menu_link->label(), $menu_link->route_name, $menu_link->route_parameters, $menu_link->options);
+            }
+          }
+          $depth++;
+        }
+      }
+      return $links;
+    }
+  }
+
+  /**
+   * Translates a string to the current language or to a given language.
+   *
+   * See the t() documentation for details.
+   */
+  protected function t($string, array $args = array(), array $options = array()) {
+    return $this->translation->translate($string, $args, $options);
+  }
+
+}
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php b/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php
index 490a0d0cd662..456e1fda4e75 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php
@@ -81,14 +81,20 @@ public function routes(RouteBuildEvent $event) {
         }
         $route = new Route(
           "$path/fields",
-          array('_form' => '\Drupal\field_ui\FieldOverview') + $defaults,
+          array(
+            '_form' => '\Drupal\field_ui\FieldOverview',
+            '_title' => 'Manage fields',
+          ) + $defaults,
           array('_permission' => 'administer ' . $entity_type . ' fields')
         );
         $collection->add("field_ui.overview_$entity_type", $route);
 
         $route = new Route(
           "$path/form-display",
-          array('_form' => '\Drupal\field_ui\FormDisplayOverview') + $defaults,
+          array(
+            '_form' => '\Drupal\field_ui\FormDisplayOverview',
+            '_title' => 'Manage form display',
+          ) + $defaults,
           array('_permission' => 'administer ' . $entity_type . ' form display')
         );
         $collection->add("field_ui.form_display_overview_$entity_type", $route);
@@ -106,7 +112,10 @@ public function routes(RouteBuildEvent $event) {
 
         $route = new Route(
           "$path/display",
-          array('_form' => '\Drupal\field_ui\DisplayOverview') + $defaults,
+          array(
+            '_form' => '\Drupal\field_ui\DisplayOverview',
+            '_title' => 'Manage display',
+          ) + $defaults,
           array('_permission' => 'administer ' . $entity_type . ' display')
         );
         $collection->add("field_ui.display_overview_$entity_type", $route);
diff --git a/core/modules/filter/filter.routing.yml b/core/modules/filter/filter.routing.yml
index 93bb987bed34..88e2711cbf69 100644
--- a/core/modules/filter/filter.routing.yml
+++ b/core/modules/filter/filter.routing.yml
@@ -17,6 +17,7 @@ filter.admin_overview:
   defaults:
     _content: '\Drupal\Core\Entity\Controller\EntityListController::listing'
     entity_type: 'filter_format'
+    _title: 'Text formats and editors'
   requirements:
     _permission: 'administer filters'
 
diff --git a/core/modules/image/lib/Drupal/image/PathProcessor/PathProcessorImageStyles.php b/core/modules/image/lib/Drupal/image/PathProcessor/PathProcessorImageStyles.php
index c0c6f312bfeb..441847608b82 100644
--- a/core/modules/image/lib/Drupal/image/PathProcessor/PathProcessorImageStyles.php
+++ b/core/modules/image/lib/Drupal/image/PathProcessor/PathProcessorImageStyles.php
@@ -46,12 +46,17 @@ public function processInbound($path, Request $request) {
     $rest = preg_replace('|^' . $path_prefix . '|', '', $path);
 
     // Get the image style, scheme and path.
-    list($image_style, $scheme, $file) = explode('/', $rest, 3);
+    if (substr_count($rest, '/') >= 2) {
+      list($image_style, $scheme, $file) = explode('/', $rest, 3);
 
-    // Set the file as query parameter.
-    $request->query->set('file', $file);
+      // Set the file as query parameter.
+      $request->query->set('file', $file);
 
-    return $path_prefix . $image_style . '/' . $scheme;
+      return $path_prefix . $image_style . '/' . $scheme;
+    }
+    else {
+      return $path;
+    }
   }
 
 }
diff --git a/core/modules/menu/menu.routing.yml b/core/modules/menu/menu.routing.yml
index aaee350462f1..21743ddfe284 100644
--- a/core/modules/menu/menu.routing.yml
+++ b/core/modules/menu/menu.routing.yml
@@ -9,6 +9,7 @@ menu.overview_page:
   path: '/admin/structure/menu'
   defaults:
     _entity_list: 'menu'
+    _title: 'Menus'
   requirements:
     _permission: 'administer menu'
 
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkBreadcrumbBuilder.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkBreadcrumbBuilder.php
deleted file mode 100644
index acece08000d8..000000000000
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkBreadcrumbBuilder.php
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\MenuLinkBreadcrumbBuilder.
- */
-
-namespace Drupal\menu_link;
-
-use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
-
-/**
- * Class to define the menu_link breadcrumb builder.
- */
-class MenuLinkBreadcrumbBuilder implements BreadcrumbBuilderInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function build(array $attributes) {
-    // @todo Rewrite the implementation.
-    // Currently the result always array.
-    return menu_get_active_breadcrumb();
-  }
-
-}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php
index 8aea21df7015..4f437ecc2107 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php
@@ -76,17 +76,6 @@ public function form(array $form, array &$form_state) {
     // item, do it here instead.
     _menu_link_translate($menu_link);
 
-    if (!$menu_link->isNew()) {
-      // Get the human-readable menu title from the given menu name.
-      $titles = menu_get_menus();
-      $current_title = $titles[$menu_link->menu_name];
-
-      // Get the current breadcrumb and add a link to that menu's overview page.
-      $breadcrumb = menu_get_active_breadcrumb();
-      $breadcrumb[] = l($current_title, 'admin/structure/menu/manage/' . $menu_link->menu_name);
-      drupal_set_breadcrumb($breadcrumb);
-    }
-
     $form['link_title'] = array(
       '#type' => 'textfield',
       '#title' => t('Menu link title'),
diff --git a/core/modules/menu_link/menu_link.module b/core/modules/menu_link/menu_link.module
index 85d8e92c012a..7e0358ee527e 100644
--- a/core/modules/menu_link/menu_link.module
+++ b/core/modules/menu_link/menu_link.module
@@ -6,6 +6,8 @@
  */
 
 use Drupal\menu_link\Entity\MenuLink;
+use Drupal\menu_link\MenuLinkInterface;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 
 function menu_link_help($path, $arg) {
   switch ($path) {
@@ -195,3 +197,19 @@ function menu_link_maintain($module, $op, $link_path, $link_title = NULL) {
       break;
   }
 }
+
+/**
+ * Implements hook_system_breadcrumb_alter().
+ */
+function menu_link_system_breadcrumb_alter(array &$breadcrumb, array $attributes, array $context) {
+  // Custom breadcrumb behaviour for editing menu links, we append a link to
+  // the menu in which the link is found.
+  if (!empty($attributes[RouteObjectInterface::ROUTE_NAME]) && $attributes[RouteObjectInterface::ROUTE_NAME] == 'menu.link_edit' && !empty($attributes['menu_link'])) {
+    $menu_link = $attributes['menu_link'];
+    if (($menu_link instanceof MenuLinkInterface) && !$menu_link->isNew()) {
+      // Add a link to the menu admin screen.
+      $menu = entity_load('menu', $menu_link->menu_name);
+      $breadcrumb[] = Drupal::l($menu->label(), 'menu.menu_edit', array('menu' => $menu->id));
+    }
+  }
+}
diff --git a/core/modules/menu_link/menu_link.services.yml b/core/modules/menu_link/menu_link.services.yml
deleted file mode 100644
index a428476c4c7d..000000000000
--- a/core/modules/menu_link/menu_link.services.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-services:
-  menu_link.breadcrumb:
-    class: Drupal\menu_link\MenuLinkBreadcrumbBuilder
-    tags:
-      - { name: breadcrumb_builder, priority: 0 }
diff --git a/core/modules/node/lib/Drupal/node/Controller/NodeController.php b/core/modules/node/lib/Drupal/node/Controller/NodeController.php
index fdc41e627cb1..d1ea5da67277 100644
--- a/core/modules/node/lib/Drupal/node/Controller/NodeController.php
+++ b/core/modules/node/lib/Drupal/node/Controller/NodeController.php
@@ -31,13 +31,6 @@ public function add(EntityInterface $node_type) {
     return node_add($node_type);
   }
 
-  /**
-   * @todo Remove node_page_view().
-   */
-  public function viewPage(NodeInterface $node) {
-    return node_page_view($node);
-  }
-
   /**
    * @todo Remove node_show().
    */
diff --git a/core/modules/node/lib/Drupal/node/Controller/NodeView.php b/core/modules/node/lib/Drupal/node/Controller/NodeView.php
new file mode 100644
index 000000000000..33181d8c3bf1
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Controller/NodeView.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Controller\NodeView.
+ */
+
+namespace Drupal\node\Controller;
+
+use Drupal\Component\Utility\String;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\node\NodeInterface;
+
+/**
+ * Returns responses for Node routes.
+ */
+class NodeView {
+
+  /**
+   * @todo Remove node_page_view().
+   */
+  public function page(NodeInterface $node) {
+    return node_page_view($node);
+  }
+
+  /**
+   * The _title_callback for the node.view route.
+   *
+   * @param \Drupal\node\NodeInterface $node
+   */
+  public function pageTitle(NodeInterface $node) {
+    return String::checkPlain($node->label());
+  }
+
+}
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index e1d2b63f3f9f..cf31cc40bcb1 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1477,10 +1477,6 @@ function node_page_view(EntityInterface $node) {
 
   $build = node_show($node);
 
-  // If there is a menu link to this node, the link becomes the last part
-  // of the active trail, and the link name becomes the page title.
-  // Thus, we must explicitly set the page title to be the node title.
-  $build['#title'] = String::checkPlain($node->label());
   return $build;
 }
 
diff --git a/core/modules/node/node.routing.yml b/core/modules/node/node.routing.yml
index c2a4ff15323a..2f8c8ab6054f 100644
--- a/core/modules/node/node.routing.yml
+++ b/core/modules/node/node.routing.yml
@@ -31,7 +31,8 @@ node.add:
 node.view:
   path: '/node/{node}'
   defaults:
-    _content: '\Drupal\node\Controller\NodeController::viewPage'
+    _content: '\Drupal\node\Controller\NodeView::page'
+    _title_callback: '\Drupal\node\Controller\NodeView::pageTitle'
   requirements:
     _entity_access: 'node.view'
 
@@ -77,6 +78,7 @@ node.overview_types:
   defaults:
     _content: '\Drupal\Core\Entity\Controller\EntityListController::listing'
     entity_type: 'node_type'
+    _title: 'Content types'
   requirements:
     _permission: 'administer content types'
 
diff --git a/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php b/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php
new file mode 100644
index 000000000000..1279a927ffda
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php
@@ -0,0 +1,239 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\PathBasedBreadcrumbBuilder.
+ */
+
+namespace Drupal\system;
+
+use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Controller\TitleResolverInterface;
+use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Routing\RequestHelper;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\Core\Access\AccessManager;
+use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Utility\LinkGeneratorInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+
+/**
+ * Class to define the menu_link breadcrumb builder.
+ */
+class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
+
+  /**
+   * The current request.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
+   * The menu link access service.
+   *
+   * @var \Drupal\Core\Access\AccessManager
+   */
+  protected $accessManager;
+
+  /**
+   * The translation manager service.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface;
+   */
+  protected $translation;
+
+  /**
+   * The menu storage controller.
+   *
+   * @var \Drupal\Core\Config\Entity\ConfigStorageController
+   */
+  protected $menuStorage;
+
+  /**
+   * The dynamic router service.
+   *
+   * @var \Symfony\Component\Routing\Matcher\RequestMatcherInterface
+   */
+  protected $router;
+
+  /**
+   * The dynamic router service.
+   *
+   * @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
+   */
+  protected $pathProcessor;
+
+  /**
+   * The link generator service.
+   *
+   * @var \Drupal\Core\Utility\LinkGeneratorInterface
+   */
+  protected $linkGenerator;
+
+  /**
+   * Site config object.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $config;
+
+  /**
+   * The title resolver.
+   *
+   * @var \Drupal\Core\Controller\TitleResolverInterface
+   */
+  protected $titleResolver;
+
+
+  /**
+   * Constructs the PathBasedBreadcrumbBuilder.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current Request object.
+   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   *   The entity manager service.
+   * @param \Drupal\Core\Access\AccessManager $access_manager
+   *   The menu link access service.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
+   *   The translation manager service.
+   * @param \Symfony\Component\Routing\Matcher\RequestMatcherInterface $router
+   *   The dynamic router service.
+   * @param \Drupal\Core\PathProcessor\InboundPathProcessorInterface $path_processor
+   *   The inbound path processor.
+   * @param \Drupal\Core\Config\ConfigFactory $config_factory
+   *   The config factory service.
+   * @param \Drupal\Core\Utility\LinkGeneratorInterface $link_generator
+   *   The link generator.
+   * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
+   *   The title resolver service.
+   */
+  public function __construct(Request $request, EntityManager $entity_manager, AccessManager $access_manager, TranslationInterface $translation, RequestMatcherInterface $router, InboundPathProcessorInterface $path_processor, ConfigFactory $config_factory, LinkGeneratorInterface $link_generator, TitleResolverInterface $title_resolver) {
+    $this->request = $request;
+    $this->accessManager = $access_manager;
+    $this->translation = $translation;
+    $this->menuStorage = $entity_manager->getStorageController('menu');
+    $this->router = $router;
+    $this->pathProcessor = $path_processor;
+    $this->config = $config_factory->get('system.site');
+    $this->linkGenerator = $link_generator;
+    $this->titleResolver = $title_resolver;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build(array $attributes) {
+    $links = array();
+
+    // General path-based breadcrumbs. Use the actual request path, prior to
+    // resolving path aliases, so the breadcrumb can be defined by simply
+    // creating a hierarchy of path aliases.
+    $path = trim($this->request->getPathInfo(), '/');
+    $path_elements = explode('/', $path);
+    $exclude = array();
+    // Don't show a link to the front-page path.
+    $front = $this->config->get('page.front');
+    $exclude[$front] = TRUE;
+    // /user is just a redirect, so skip it.
+    // @todo Find a better way to deal with /user.
+    $exclude['user'] = TRUE;
+    while (count($path_elements) > 1) {
+      array_pop($path_elements);
+      // Copy the path elements for up-casting.
+      $route_request = $this->getRequestForPath(implode('/', $path_elements), $exclude);
+      if ($route_request) {
+        if (!$route_request->attributes->get('_legacy')) {
+          $route_name = $route_request->attributes->get(RouteObjectInterface::ROUTE_NAME);
+          // Note that the parameters don't really matter here since we're
+          // passing in the request which already has the upcast attributes.
+          $parameters = array();
+          $access = $this->accessManager->checkNamedRoute($route_name, $parameters, $route_request);
+          if ($access) {
+            $title = $this->titleResolver->getTitle($route_request, $route_request->attributes->get(RouteObjectInterface::ROUTE_OBJECT));
+          }
+        }
+        // @todo - remove this once all of core is converted to the new router.
+        else {
+          $menu_item = $route_request->attributes->get('_drupal_menu_item');
+          // Skip the breadcrumb step for menu items linking to the parent item.
+          if (($menu_item['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT) {
+            continue;
+          }
+          $access = $menu_item['access'];
+          $title = $menu_item['title'];
+        }
+        if ($access) {
+          if (!$title) {
+            // Fallback to using the raw path component as the title if the
+            // route is missing a _title or _title_callback attribute.
+            $title = str_replace(array('-', '_'), ' ', Unicode::ucfirst(end($path_elements)));
+          }
+          // @todo Replace with a #type => link render element so that the alter
+          // hook can work with the actual data.
+          $links[] = l($title, $route_request->attributes->get('_system_path'));
+        }
+      }
+
+    }
+    if ($path && $path != $front) {
+      // Add the Home link, except for the front page.
+      $links[] = $this->linkGenerator->generate($this->t('Home'), '<front>');
+    }
+    return array_reverse($links);
+  }
+
+  /**
+   * Matches a path in the router.
+   *
+   * @param string $path
+   *   The request path.
+   * @param array $exclude
+   *   An array of paths or system paths to skip.
+   *
+   * @return \Symfony\Component\HttpFoundation\Request
+   *   A populated request object or NULL if the patch couldn't be matched.
+   */
+  protected function getRequestForPath($path, array $exclude) {
+    if (!empty($exclude[$path])) {
+      return NULL;
+    }
+    // @todo Use the RequestHelper once https://drupal.org/node/2090293 is
+    //   fixed.
+    $request = Request::create($this->request->getBaseUrl() . '/' . $path);
+    // Find the system path by resolving aliases, language prefix, etc.
+    $processed = $this->pathProcessor->processInbound($path, $request);
+    if (empty($processed) || !empty($exclude[$processed])) {
+      // This resolves to the front page, which we already add.
+      return NULL;
+    }
+    $request->attributes->set('_system_path', $processed);
+    // Attempt to match this path to provide a fully built request.
+    try {
+      $request->attributes->add($this->router->matchRequest($request));
+      return $request;
+    }
+    catch (NotFoundHttpException $e) {
+      return NULL;
+    }
+    catch (ResourceNotFoundException $e) {
+      return NULL;
+    }
+  }
+
+  /**
+   * Translates a string to the current language or to a given language.
+   *
+   * See the t() documentation for details.
+   */
+  protected function t($string, array $args = array(), array $options = array()) {
+    return $this->translation->translate($string, $args, $options);
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php
index 23e8cf9b0b02..c4a12991301b 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\system\Tests\Menu;
 
+use Drupal\Component\Utility\Unicode;
+
 /**
  * Menu breadcrumbs related tests.
  */
@@ -56,34 +58,11 @@ function setUp() {
    */
   function testBreadCrumbs() {
     // Prepare common base breadcrumb elements.
-    $home = array('<front>' => 'Home');
+    $home = array('' => 'Home');
     $admin = $home + array('admin' => t('Administration'));
     $config = $admin + array('admin/config' => t('Configuration'));
     $type = 'article';
 
-    // Verify breadcrumbs for default local tasks.
-    $expected = array(
-      'menu-test' => t('Menu test root'),
-    );
-    $title = t('Breadcrumbs test: Local tasks');
-    $trail = $home + $expected;
-    $tree = $expected + array(
-      'menu-test/breadcrumb/tasks' => $title,
-    );
-    $this->assertBreadcrumb('menu-test/breadcrumb/tasks', $trail, $title, $tree);
-    $this->assertBreadcrumb('menu-test/breadcrumb/tasks/first', $trail, $title, $tree);
-    $this->assertBreadcrumb('menu-test/breadcrumb/tasks/first/first', $trail, $title, $tree);
-    $trail += array(
-      'menu-test/breadcrumb/tasks' => t('Breadcrumbs test: Local tasks'),
-    );
-    $this->assertBreadcrumb('menu-test/breadcrumb/tasks/first/second', $trail, $title, $tree);
-    $this->assertBreadcrumb('menu-test/breadcrumb/tasks/second', $trail, $title, $tree);
-    $this->assertBreadcrumb('menu-test/breadcrumb/tasks/second/first', $trail, $title, $tree);
-    $trail += array(
-      'menu-test/breadcrumb/tasks/second' => t('Second'),
-    );
-    $this->assertBreadcrumb('menu-test/breadcrumb/tasks/second/second', $trail, $title, $tree);
-
     // Verify Taxonomy administration breadcrumbs.
     $trail = $admin + array(
       'admin/structure' => t('Structure'),
@@ -160,8 +139,10 @@ function testBreadCrumbs() {
     );
     $this->assertBreadcrumb('admin/config/content/formats/add', $trail);
     $this->assertBreadcrumb("admin/config/content/formats/manage/$format_id", $trail);
+    // @todo Remove this part once we have a _title_callback, see
+    //   https://drupal.org/node/2076085.
     $trail += array(
-      "admin/config/content/formats/manage/$format_id" => $format->name,
+      "admin/config/content/formats/manage/$format_id" => Unicode::ucfirst(Unicode::strtolower($format->name)),
     );
     $this->assertBreadcrumb("admin/config/content/formats/manage/$format_id/disable", $trail);
 
@@ -208,49 +189,9 @@ function testBreadCrumbs() {
           'plid' => 0,
         )),
       ));
-      $nid2 = $node2->id();
-
-      $trail = $home;
-      $tree = array(
-        "node/$nid2" => $node2->menu['link_title'],
-      );
-      $this->assertBreadcrumb("node/$nid2", $trail, $node2->getTitle(), $tree);
-      $trail += array(
-        "node/$nid2" => $node2->menu['link_title'],
-      );
-      $this->assertBreadcrumb("node/$nid2/edit", $trail);
-
-      // Create a child node in the current menu.
-      $title = $this->randomName();
-      $node3 = $this->drupalCreateNode(array(
-        'type' => $type,
-        'title' => $title,
-        'menu' => entity_create('menu_link', array(
-          'enabled' => 1,
-          'link_title' => 'Child ' . $title,
-          'description' => '',
-          'menu_name' => $menu,
-          'plid' => $node2->menu['mlid'],
-        )),
-      ));
-      $nid3 = $node3->id();
-
-      $this->assertBreadcrumb("node/$nid3", $trail, $node3->getTitle(), $tree, FALSE);
-      $trail += array(
-        "node/$nid3" => $node3->menu['link_title'],
-      );
-      $tree += array(
-        "node/$nid3" => $node3->menu['link_title'],
-      );
-      $this->assertBreadcrumb("node/$nid3/edit", $trail);
-
-      // Verify that node listing page still contains "Home" only.
-      $trail = array();
-      $this->assertBreadcrumb('node', $trail);
 
       if ($menu == 'tools') {
         $parent = $node2;
-        $child = $node3;
       }
     }
 
@@ -276,14 +217,9 @@ function testBreadCrumbs() {
     $tree = $expected + array(
       'node/' . $parent->id() => $parent->menu['link_title'],
     );
-    $this->assertBreadcrumb(NULL, $trail, $parent->getTitle(), $tree);
     $trail += array(
       'node/' . $parent->id() => $parent->menu['link_title'],
     );
-    $tree += array(
-      'node/' . $parent->id() => $child->menu['link_title'],
-    );
-    $this->assertBreadcrumb('node/' . $child->id(), $trail, $child->getTitle(), $tree);
 
     // Add a taxonomy term/tag to last node, and add a link for that term to the
     // Tools menu.
@@ -443,38 +379,6 @@ function testBreadCrumbs() {
     );
     // $this->assertBreadcrumb('user/' . $this->admin_user->id(), $trail, $link_admin_user['link_title'], $tree);
 
-    $this->drupalLogin($this->admin_user);
-    $trail += array(
-      $link_admin_user['link_path'] => $link_admin_user['link_title'],
-    );
-    $this->assertBreadcrumb('user/' . $this->admin_user->id() . '/edit', $trail, $link_admin_user['link_title'], $tree, FALSE);
-
-    // Move 'user/%' below 'user' and verify again.
-    $edit = array(
-      'parent' => "$menu:{$link_user['mlid']}",
-    );
-    $this->drupalPostForm("admin/structure/menu/item/{$link_admin_user['mlid']}/edit", $edit, t('Save'));
-
-    $this->drupalLogout();
-    $trail = $home;
-    $tree = array(
-      $link_user['link_path'] => $link_user['link_title'],
-    );
-    $this->assertBreadcrumb('user', $trail, $link_user['link_title'], $tree);
-    $trail += array(
-      $link_user['link_path'] => $link_user['link_title'],
-    );
-    $tree += array(
-      $link_admin_user['link_path'] => $link_admin_user['link_title'],
-    );
-    // $this->assertBreadcrumb('user/' . $this->admin_user->id(), $trail, $link_admin_user['link_title'], $tree);
-
-    $this->drupalLogin($this->admin_user);
-    $trail += array(
-      $link_admin_user['link_path'] => $link_admin_user['link_title'],
-    );
-    $this->assertBreadcrumb('user/' . $this->admin_user->id() . '/edit', $trail, $link_admin_user['link_title'], $tree, FALSE);
-
     // Create an only slightly privileged user being able to access site reports
     // but not administration pages.
     $this->web_user = $this->drupalCreateUser(array(
@@ -483,16 +387,19 @@ function testBreadCrumbs() {
     $this->drupalLogin($this->web_user);
 
     // Verify that we can access recent log entries, there is a corresponding
-    // page title, and that the breadcrumb is empty (because the user is not
-    // able to access "Administer", so the trail cannot recurse into it).
-    $trail = array();
+    // page title, and that the breadcrumb is just the Home link (because the
+    // user is not able to access "Administer".
+    $trail = $home;
     $this->assertBreadcrumb('admin', $trail, t('Access denied'));
     $this->assertResponse(403);
 
-    $trail = $home;
+    // Since the 'admin' path is not accessible, we still expect only the Home
+    // link.
     $this->assertBreadcrumb('admin/reports', $trail, t('Reports'));
     $this->assertNoResponse(403);
 
+    // Since the Reports page is accessible, that will show.
+    $trail += array('admin/reports' => t('Reports'));
     $this->assertBreadcrumb('admin/reports/dblog', $trail, t('Recent log messages'));
     $this->assertNoResponse(403);
   }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuTestBase.php
index 7d23727d564a..d205b133c029 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuTestBase.php
@@ -59,8 +59,9 @@ protected function assertBreadcrumbParts($trail) {
     // Compare paths with actual breadcrumb.
     $parts = $this->getBreadcrumbParts();
     $pass = TRUE;
-    // There may be more than one breadcrumb on the page.
-    while (!empty($parts)) {
+    // There may be more than one breadcrumb on the page. If $trail is empty
+    // this test would go into an infinite loop, so we need to check that too.
+    while ($trail && !empty($parts)) {
       foreach ($trail as $path => $title) {
         $url = url($path);
         $part = array_shift($parts);
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuTranslateTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuTranslateTest.php
index 33aa51ada658..8c2728775abf 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuTranslateTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuTranslateTest.php
@@ -45,7 +45,11 @@ public function testMenuTranslate() {
     // Attempt to access a restricted local task.
     $this->drupalGet('foo/asdf/c');
     $this->assertResponse(403);
-    $this->assertNoLinkByHref('foo/asdf');
+    $elements = $this->xpath('//ul[@class=:class]/li/a[@href=:href]', array(
+      ':class' => 'tabs primary',
+      ':href' => url('foo/asdf'),
+    ));
+    $this->assertTrue(empty($elements), 'No tab linking to foo/asdf found');
     $this->assertNoLinkByHref('foo/asdf/b');
     $this->assertNoLinkByHref('foo/asdf/c');
   }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/TrailTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/TrailTest.php
index 111c118cf2eb..409f3aae38ec 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/TrailTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/TrailTest.php
@@ -39,69 +39,6 @@ function setUp() {
     $this->drupalPlaceBlock('system_menu_block:admin');
   }
 
-  /**
-   * Tests active trails are properly affected by menu_tree_set_path().
-   */
-  function testMenuTreeSetPath() {
-    $home = array('<front>' => 'Home');
-    $config_tree = array(
-      'admin' => t('Administration'),
-      'admin/config' => t('Configuration'),
-    );
-    $config = $home + $config_tree;
-
-    // The menu_test_menu_tree_set_path system variable controls whether or not
-    // the menu_test_menu_trail_callback() callback (used by all paths in these
-    // tests) issues an overriding call to menu_trail_set_path().
-    $test_menu_path = array(
-      'menu_name' => 'admin',
-      'path' => 'admin/config/system/site-information',
-    );
-
-    $breadcrumb = $home + array(
-      'menu-test' => t('Menu test root'),
-    );
-    $tree = array(
-      'menu-test' => t('Menu test root'),
-      'menu-test/menu-trail' => t('Menu trail - Case 1'),
-    );
-
-    // Test the tree generation for the Tools menu.
-    \Drupal::state()->delete('menu_test.menu_tree_set_path');
-    $this->assertBreadcrumb('menu-test/menu-trail', $breadcrumb, t('Menu trail - Case 1'), $tree);
-
-    // Override the active trail for the Administration tree; it should not
-    // affect the Tools tree.
-    \Drupal::state()->set('menu_test.menu_tree_set_path', $test_menu_path);
-    $this->assertBreadcrumb('menu-test/menu-trail', $breadcrumb, t('Menu trail - Case 1'), $tree);
-
-    $breadcrumb = $config + array(
-      'admin/config/development' => t('Development'),
-    );
-    $tree = $config_tree + array(
-      'admin/config/development' => t('Development'),
-      'admin/config/development/menu-trail' => t('Menu trail - Case 2'),
-    );
-
-    $override_breadcrumb = $config + array(
-      'admin/config/system' => t('System'),
-      'admin/config/system/site-information' => t('Site information'),
-    );
-    $override_tree = $config_tree + array(
-      'admin/config/system' => t('System'),
-      'admin/config/system/site-information' => t('Site information'),
-    );
-
-    // Test the tree generation for the Administration menu.
-    \Drupal::state()->delete('menu_test.menu_tree_set_path');
-    $this->assertBreadcrumb('admin/config/development/menu-trail', $breadcrumb, t('Menu trail - Case 2'), $tree);
-
-    // Override the active trail for the Administration tree; it should affect
-    // the breadcrumbs and Administration tree.
-    \Drupal::state()->set('menu_test.menu_tree_set_path', $test_menu_path);
-    $this->assertBreadcrumb('admin/config/development/menu-trail', $override_breadcrumb, t('Menu trail - Case 2'), $override_tree);
-  }
-
   /**
    * Tests that the active trail works correctly on custom 403 and 404 pages.
    */
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeAccessTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeAccessTest.php
index 263e2fcd8c10..df10877630d3 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeAccessTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeAccessTest.php
@@ -33,6 +33,13 @@ class TreeAccessTest extends DrupalUnitTestBase {
    */
   protected $routeCollection;
 
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('menu_link');
+
   public static function getInfo() {
     return array(
       'name' => 'Menu tree access',
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 150a42c55432..0bde16eb46d0 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -940,53 +940,6 @@ function hook_local_task_alter(&$local_tasks) {
   unset($local_tasks['example_plugin_id']);
 }
 
-/**
- * Alter links in the active trail before it is rendered as the breadcrumb.
- *
- * This hook is invoked by menu_get_active_breadcrumb() and allows alteration
- * of the breadcrumb links for the current page, which may be preferred instead
- * of setting a custom breadcrumb via drupal_set_breadcrumb().
- *
- * Implementations should take into account that menu_get_active_breadcrumb()
- * subsequently performs the following adjustments to the active trail *after*
- * this hook has been invoked:
- * - The last link in $active_trail is removed, if its 'href' is identical to
- *   the 'href' of $item. This happens, because the breadcrumb normally does
- *   not contain a link to the current page.
- * - The (second to) last link in $active_trail is removed, if the current $item
- *   is a MENU_DEFAULT_LOCAL_TASK. This happens in order to do not show a link
- *   to the current page, when being on the path for the default local task;
- *   e.g. when being on the path node/%/view, the breadcrumb should not contain
- *   a link to node/%.
- *
- * Each link in the active trail must contain:
- * - title: The localized title of the link.
- * - href: The system path to link to.
- * - localized_options: An array of options to pass to url().
- *
- * @param $active_trail
- *   An array containing breadcrumb links for the current page.
- * @param $item
- *   The menu router item of the current page.
- *
- * @see drupal_set_breadcrumb()
- * @see menu_get_active_breadcrumb()
- * @see menu_get_active_trail()
- * @see menu_set_active_trail()
- */
-function hook_menu_breadcrumb_alter(&$active_trail, $item) {
-  // Always display a link to the current page by duplicating the last link in
-  // the active trail. This means that menu_get_active_breadcrumb() will remove
-  // the last link (for the current page), but since it is added once more here,
-  // it will appear.
-  if (!drupal_is_front_page()) {
-    $end = end($active_trail);
-    if ($item['href'] == $end['href']) {
-      $active_trail[] = $end;
-    }
-  }
-}
-
 /**
  * Alter contextual links before they are rendered.
  *
@@ -1398,6 +1351,29 @@ function hook_module_implements_alter(&$implementations, $hook) {
   }
 }
 
+/**
+ * Perform alterations to the breadcrumb built by the BreadcrumbManager.
+ *
+ * @param array $breadcrumb
+ *   An array of breadcrumb link a tags, returned by the breadcrumb manager
+ *   build method, for example
+ *   @code
+ *     array('<a href="/">Home</a>');
+ *   @endcode
+ * @param array $attributes
+ *   Attributes representing the current page, coming from
+ *   \Drupal::request()->attributes.
+ * @param array $context
+ *   May include the following key:
+ *   - builder: the instance of
+ *     \Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface that constructed this
+ *     breadcrumb, or NULL if no builder acted based on the current attributes.
+ */
+function hook_system_breadcrumb_alter(array &$breadcrumb, array $attributes, array $context) {
+  // Add an item to the end of the breadcrumb.
+  $breadcrumb[] = Drupal::l(t('Text'), 'example_route_name');
+}
+
 /**
  * Return additional themes provided by modules.
  *
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 7b284ae84c78..508d59c4d752 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -9,6 +9,7 @@ system.admin:
   path: '/admin'
   defaults:
     _content: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+    _title: 'Administration'
   requirements:
     _permission: 'access administration pages'
 
@@ -16,6 +17,7 @@ system.admin_structure:
   path: '/admin/structure'
   defaults:
     _content: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+    _title: 'Structure'
   requirements:
     _permission: 'access administration pages'
 
@@ -23,6 +25,7 @@ system.admin_reports:
   path: '/admin/reports'
   defaults:
     _content: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+    _title: 'Reports'
   requirements:
     _permission: 'access site reports'
 
@@ -30,6 +33,7 @@ system.admin_config_media:
   path: '/admin/config/media'
   defaults:
     _content: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+    _title: 'Media'
   requirements:
     _permission: 'access administration pages'
 
@@ -37,6 +41,7 @@ system.admin_config_services:
   path: '/admin/config/services'
   defaults:
     _content: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+    _title: 'Web services'
   requirements:
     _permission: 'access administration pages'
 
@@ -44,6 +49,7 @@ system.admin_config_development:
   path: '/admin/config/development'
   defaults:
     _content: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+    _title: 'Development'
   requirements:
     _permission: 'access administration pages'
 
@@ -51,6 +57,7 @@ system.admin_config_regional:
   path: '/admin/config/regional'
   defaults:
     _content: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+    _title: 'Regional and language'
   requirements:
     _permission: 'access administration pages'
 
@@ -58,6 +65,7 @@ system.admin_config_search:
   path: '/admin/config/search'
   defaults:
     _content: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+    _title: 'Search and metadata'
   requirements:
     _permission: 'access administration pages'
 
@@ -65,6 +73,7 @@ system.admin_config_system:
   path: '/admin/config/system'
   defaults:
     _content: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+    _title: 'System'
   requirements:
     _permission: 'access administration pages'
 
@@ -72,6 +81,7 @@ system.admin_config_ui:
   path: '/admin/config/user-interface'
   defaults:
     _content: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+    _title: 'User interface'
   requirements:
     _permission: 'access administration pages'
 
@@ -79,6 +89,7 @@ system.admin_config_workflow:
   path: '/admin/config/workflow'
   defaults:
     _content: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+    _title: 'Workflow'
   requirements:
     _permission: 'access administration pages'
 
@@ -86,6 +97,7 @@ system.admin_config_content:
   path: '/admin/config/content'
   defaults:
     _content: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+    _title: 'Content authoring'
   requirements:
     _permission: 'access administration pages'
 
@@ -342,6 +354,7 @@ system.admin_config:
   path: '/admin/config'
   defaults:
     _content: '\Drupal\system\Controller\SystemController::overview'
+    _title: 'Configuration'
   requirements:
     _permission: 'access administration pages'
 
diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml
index f1838b554932..ae137daf14be 100644
--- a/core/modules/system/system.services.yml
+++ b/core/modules/system/system.services.yml
@@ -10,6 +10,11 @@ services:
     class: Drupal\system\LegacyBreadcrumbBuilder
     tags:
       - {name: breadcrumb_builder, priority: 500}
+  system.breadcrumb.default:
+    class: Drupal\system\PathBasedBreadcrumbBuilder
+    arguments: ['@request', '@entity.manager', '@access_manager', '@string_translation', '@router', '@path_processor_manager', '@config.factory', '@link_generator',  '@title_resolver']
+    tags:
+      - { name: breadcrumb_builder, priority: 0 }
   path_processor.files:
     class: Drupal\system\PathProcessor\PathProcessorFiles
     tags:
diff --git a/core/modules/taxonomy/taxonomy.routing.yml b/core/modules/taxonomy/taxonomy.routing.yml
index 8e024a0fc636..43a0c2a3ce55 100644
--- a/core/modules/taxonomy/taxonomy.routing.yml
+++ b/core/modules/taxonomy/taxonomy.routing.yml
@@ -2,6 +2,7 @@ taxonomy.vocabulary_list:
   path: '/admin/structure/taxonomy'
   defaults:
     _entity_list: 'taxonomy_vocabulary'
+    _title: 'Taxonomy'
   requirements:
     _permission: 'administer taxonomy'
 
-- 
GitLab