diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index a1c8a01..3af5bb9 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -322,594 +322,6 @@ function _menu_link_translate(&$item) {
 }
 
 /**
- * Renders a menu tree based on the current path.
- *
- * @param $menu_name
- *   The name of the menu.
- *
- * @return
- *   A structured array representing the specified menu on the current page, to
- *   be rendered by drupal_render().
- */
-function menu_tree($menu_name) {
-  $menu_output = &drupal_static(__FUNCTION__, array());
-
-  if (!isset($menu_output[$menu_name])) {
-    $tree = menu_tree_page_data($menu_name);
-    $menu_output[$menu_name] = menu_tree_output($tree);
-  }
-  return $menu_output[$menu_name];
-}
-
-/**
- * Returns a rendered menu tree.
- *
- * The menu item's LI element is given one of the following classes:
- * - expanded: The menu item is showing its submenu.
- * - collapsed: The menu item has a submenu which is not shown.
- * - leaf: The menu item has no submenu.
- *
- * @param $tree
- *   A data structure representing the tree as returned from menu_tree_data.
- *
- * @return
- *   A structured array to be rendered by drupal_render().
- */
-function menu_tree_output($tree) {
-  $build = array();
-  $items = array();
-
-  // Pull out just the menu links we are going to render so that we
-  // get an accurate count for the first/last classes.
-  foreach ($tree as $data) {
-    if ($data['link']['access'] && !$data['link']['hidden']) {
-      $items[] = $data;
-    }
-  }
-
-  foreach ($items as $data) {
-    $class = array();
-    // Set a class for the <li>-tag. Since $data['below'] may contain local
-    // tasks, only set 'expanded' class if the link also has children within
-    // the current menu.
-    if ($data['link']['has_children'] && $data['below']) {
-      $class[] = 'expanded';
-    }
-    elseif ($data['link']['has_children']) {
-      $class[] = 'collapsed';
-    }
-    else {
-      $class[] = 'leaf';
-    }
-    // Set a class if the link is in the active trail.
-    if ($data['link']['in_active_trail']) {
-      $class[] = 'active-trail';
-      $data['link']['localized_options']['attributes']['class'][] = 'active-trail';
-    }
-
-    // Allow menu-specific theme overrides.
-    $element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
-    $element['#attributes']['class'] = $class;
-    $element['#title'] = $data['link']['title'];
-    // @todo Use route name and parameters to generate the link path, unless
-    //    it is external.
-    $element['#href'] = $data['link']['link_path'];
-    $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array();
-    $element['#below'] = $data['below'] ? menu_tree_output($data['below']) : $data['below'];
-    $element['#original_link'] = $data['link'];
-    // Index using the link's unique mlid.
-    $build[$data['link']['mlid']] = $element;
-  }
-  if ($build) {
-    // Make sure drupal_render() does not re-order the links.
-    $build['#sorted'] = TRUE;
-    // Add the theme wrapper for outer markup.
-    // Allow menu-specific theme overrides.
-    $build['#theme_wrappers'][] = 'menu_tree__' . strtr($data['link']['menu_name'], '-', '_');
-    // Set cache tag.
-    $menu_name = $data['link']['menu_name'];
-    $build['#cache']['tags']['menu'][$menu_name] = $menu_name;
-  }
-
-  return $build;
-}
-
-/**
- * Gets the data structure representing a named menu tree.
- *
- * Since this can be the full tree including hidden items, the data returned
- * may be used for generating an an admin interface or a select.
- *
- * @param $menu_name
- *   The named menu links to return
- * @param $link
- *   A fully loaded menu link, or NULL. If a link is supplied, only the
- *   path to root will be included in the returned tree - as if this link
- *   represented the current page in a visible menu.
- * @param $max_depth
- *   Optional maximum depth of links to retrieve. Typically useful if only one
- *   or two levels of a sub tree are needed in conjunction with a non-NULL
- *   $link, in which case $max_depth should be greater than $link['depth'].
- *
- * @return
- *   An tree of menu links in an array, in the order they should be rendered.
- */
-function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) {
-  $tree = &drupal_static(__FUNCTION__, array());
-  $language_interface = \Drupal::languageManager()->getCurrentLanguage();
-
-  // Use $mlid as a flag for whether the data being loaded is for the whole tree.
-  $mlid = isset($link['mlid']) ? $link['mlid'] : 0;
-  // Generate a cache ID (cid) specific for this $menu_name, $link, $language, and depth.
-  $cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $language_interface->id . ':' . (int) $max_depth;
-
-  if (!isset($tree[$cid])) {
-    // If the static variable doesn't have the data, check {cache_menu}.
-    $cache = \Drupal::cache('menu')->get($cid);
-    if ($cache && isset($cache->data)) {
-      // If the cache entry exists, it contains the parameters for
-      // menu_build_tree().
-      $tree_parameters = $cache->data;
-    }
-    // If the tree data was not in the cache, build $tree_parameters.
-    if (!isset($tree_parameters)) {
-      $tree_parameters = array(
-        'min_depth' => 1,
-        'max_depth' => $max_depth,
-      );
-      if ($mlid) {
-        // The tree is for a single item, so we need to match the values in its
-        // p columns and 0 (the top level) with the plid values of other links.
-        $parents = array(0);
-        for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
-          if (!empty($link["p$i"])) {
-            $parents[] = $link["p$i"];
-          }
-        }
-        $tree_parameters['expanded'] = $parents;
-        $tree_parameters['active_trail'] = $parents;
-        $tree_parameters['active_trail'][] = $mlid;
-      }
-
-      // Cache the tree building parameters using the page-specific cid.
-      \Drupal::cache('menu')->set($cid, $tree_parameters, Cache::PERMANENT, array('menu' => $menu_name));
-    }
-
-    // Build the tree using the parameters; the resulting tree will be cached
-    // by _menu_build_tree()).
-    $tree[$cid] = menu_build_tree($menu_name, $tree_parameters);
-  }
-
-  return $tree[$cid];
-}
-
-/**
- * Sets the path for determining the active trail of the specified menu tree.
- *
- * This path will also affect the breadcrumbs under some circumstances.
- * Breadcrumbs are built using the preferred link returned by
- * menu_link_get_preferred(). If the preferred link is inside one of the menus
- * specified in calls to menu_tree_set_path(), the preferred link will be
- * overridden by the corresponding path returned by menu_tree_get_path().
- *
- * Setting this path does not affect the main content; for that use
- * menu_set_active_item() instead.
- *
- * @param $menu_name
- *   The name of the affected menu tree.
- * @param $path
- *   The path to use when finding the active trail.
- */
-function menu_tree_set_path($menu_name, $path = NULL) {
-  $paths = &drupal_static(__FUNCTION__);
-  if (isset($path)) {
-    $paths[$menu_name] = $path;
-  }
-  return isset($paths[$menu_name]) ? $paths[$menu_name] : NULL;
-}
-
-/**
- * Gets the path for determining the active trail of the specified menu tree.
- *
- * @param $menu_name
- *   The menu name of the requested tree.
- *
- * @return
- *   A string containing the path. If no path has been specified with
- *   menu_tree_set_path(), NULL is returned.
- */
-function menu_tree_get_path($menu_name) {
-  return menu_tree_set_path($menu_name);
-}
-
-/**
- * Gets the data structure for a named menu tree, based on the current page.
- *
- * The tree order is maintained by storing each parent in an individual
- * field, see http://drupal.org/node/141866 for more.
- *
- * @param $menu_name
- *   The named menu links to return.
- * @param $max_depth
- *   (optional) The maximum depth of links to retrieve.
- * @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.
- *
- * @return
- *   An array of menu links, in the order they should be rendered. The array
- *   is a list of associative arrays -- these have two keys, link and below.
- *   link is a menu item, ready for theming as a link. Below represents the
- *   submenu below the link if there is one, and it is a subtree that has the
- *   same structure described for the top-level array.
- */
-function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail = FALSE) {
-  $tree = &drupal_static(__FUNCTION__, array());
-
-  $language_interface = \Drupal::languageManager()->getCurrentLanguage();
-
-  // Check if the active trail has been overridden for this menu tree.
-  $active_path = menu_tree_get_path($menu_name);
-  // Load the request corresponding to the current page.
-  $request = \Drupal::request();
-  $system_path = NULL;
-  if ($route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME)) {
-    // @todo https://drupal.org/node/2068471 is adding support so we can tell
-    // if this is called on a 404/403 page.
-    $system_path = $request->attributes->get('_system_path');
-    $page_not_403 = 1;
-  }
-  if (isset($system_path)) {
-    if (isset($max_depth)) {
-      $max_depth = min($max_depth, MENU_MAX_DEPTH);
-    }
-    // Generate a cache ID (cid) specific for this page.
-    $cid = 'links:' . $menu_name . ':page:' . $system_path . ':' . $language_interface->id . ':' . $page_not_403 . ':' . (int) $max_depth;
-    // If we are asked for the active trail only, and $menu_name has not been
-    // built and cached for this page yet, then this likely means that it
-    // won't be built anymore, as this function is invoked from
-    // 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 active trail only.
-    // @see menu_set_active_trail()
-    if (!isset($tree[$cid]) && $only_active_trail) {
-      $cid .= ':trail';
-    }
-
-    if (!isset($tree[$cid])) {
-      // If the static variable doesn't have the data, check {cache_menu}.
-      $cache = \Drupal::cache('menu')->get($cid);
-      if ($cache && isset($cache->data)) {
-        // If the cache entry exists, it contains the parameters for
-        // menu_build_tree().
-        $tree_parameters = $cache->data;
-      }
-      // If the tree data was not in the cache, build $tree_parameters.
-      if (!isset($tree_parameters)) {
-        $tree_parameters = array(
-          'min_depth' => 1,
-          'max_depth' => $max_depth,
-        );
-        // Parent mlids; used both as key and value to ensure uniqueness.
-        // We always want all the top-level links with plid == 0.
-        $active_trail = array(0 => 0);
-
-        // If this page is accessible to the current user, build the tree
-        // parameters accordingly.
-        if ($page_not_403) {
-          // Find a menu link corresponding to the current path. If $active_path
-          // is NULL, let menu_link_get_preferred() determine the path.
-          if ($active_link = menu_link_get_preferred($active_path, $menu_name)) {
-            // The active link may only be taken into account to build the
-            // active trail, if it resides in the requested menu. Otherwise,
-            // we'd needlessly re-run _menu_build_tree() queries for every menu
-            // on every page.
-            if ($active_link['menu_name'] == $menu_name) {
-              // Use all the coordinates, except the last one because there
-              // can be no child beyond the last column.
-              for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
-                if ($active_link['p' . $i]) {
-                  $active_trail[$active_link['p' . $i]] = $active_link['p' . $i];
-                }
-              }
-              // If we are asked to build links for the active trail only, skip
-              // the entire 'expanded' handling.
-              if ($only_active_trail) {
-                $tree_parameters['only_active_trail'] = TRUE;
-              }
-            }
-          }
-          $parents = $active_trail;
-
-          $expanded = \Drupal::state()->get('menu_expanded');
-          // Check whether the current menu has any links set to be expanded.
-          if (!$only_active_trail && $expanded && in_array($menu_name, $expanded)) {
-            // Collect all the links set to be expanded, and then add all of
-            // their children to the list as well.
-            do {
-              $query = \Drupal::entityQuery('menu_link')
-                ->condition('menu_name', $menu_name)
-                ->condition('expanded', 1)
-                ->condition('has_children', 1)
-                ->condition('plid', $parents, 'IN')
-                ->condition('mlid', $parents, 'NOT IN');
-              $result = $query->execute();
-              $parents += $result;
-            } while (!empty($result));
-          }
-          $tree_parameters['expanded'] = $parents;
-          $tree_parameters['active_trail'] = $active_trail;
-        }
-        // If access is denied, we only show top-level links in menus.
-        else {
-          $tree_parameters['expanded'] = $active_trail;
-          $tree_parameters['active_trail'] = $active_trail;
-        }
-        // Cache the tree building parameters using the page-specific cid.
-        \Drupal::cache('menu')->set($cid, $tree_parameters, Cache::PERMANENT, array('menu' => $menu_name));
-      }
-
-      // Build the tree using the parameters; the resulting tree will be cached
-      // by _menu_build_tree().
-      $tree[$cid] = menu_build_tree($menu_name, $tree_parameters);
-    }
-    return $tree[$cid];
-  }
-
-  return array();
-}
-
-/**
- * Builds a menu tree, translates links, and checks access.
- *
- * @param $menu_name
- *   The name of the menu.
- * @param $parameters
- *   (optional) An associative array of build parameters. Possible keys:
- *   - expanded: An array of parent link ids to return only menu links that are
- *     children of one of the plids in this list. If empty, the whole menu tree
- *     is built, unless 'only_active_trail' is TRUE.
- *   - 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.
- *   - 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).
- *   - max_depth: The maximum depth of menu links in the resulting tree.
- *   - conditions: An associative array of custom database select query
- *     condition key/value pairs; see _menu_build_tree() for the actual query.
- *
- * @return
- *   A fully built menu tree.
- */
-function menu_build_tree($menu_name, array $parameters = array()) {
-  // Build the menu tree.
-  $data = _menu_build_tree($menu_name, $parameters);
-  // Check access for the current user to each item in the tree.
-  menu_tree_check_access($data['tree'], $data['node_links']);
-  return $data['tree'];
-}
-
-/**
- * Builds a menu tree.
- *
- * This function may be used build the data for a menu tree only, for example
- * to further massage the data manually before further processing happens.
- * menu_tree_check_access() needs to be invoked afterwards.
- *
- * @see menu_build_tree()
- */
-function _menu_build_tree($menu_name, array $parameters = array()) {
-  // Static cache of already built menu trees.
-  $trees = &drupal_static(__FUNCTION__, array());
-  $language_interface = \Drupal::languageManager()->getCurrentLanguage();
-
-  // Build the cache id; sort parents to prevent duplicate storage and remove
-  // default parameter values.
-  if (isset($parameters['expanded'])) {
-    sort($parameters['expanded']);
-  }
-  $tree_cid = 'links:' . $menu_name . ':tree-data:' . $language_interface->id . ':' . hash('sha256', serialize($parameters));
-
-  // If we do not have this tree in the static cache, check {cache_menu}.
-  if (!isset($trees[$tree_cid])) {
-    $cache = \Drupal::cache('menu')->get($tree_cid);
-    if ($cache && isset($cache->data)) {
-      $trees[$tree_cid] = $cache->data;
-    }
-  }
-
-  if (!isset($trees[$tree_cid])) {
-    $query = \Drupal::entityQuery('menu_link');
-    for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
-      $query->sort('p' . $i, 'ASC');
-    }
-    $query->condition('menu_name', $menu_name);
-    if (!empty($parameters['expanded'])) {
-      $query->condition('plid', $parameters['expanded'], 'IN');
-    }
-    elseif (!empty($parameters['only_active_trail'])) {
-      $query->condition('mlid', $parameters['active_trail'], 'IN');
-    }
-    $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1);
-    if ($min_depth != 1) {
-      $query->condition('depth', $min_depth, '>=');
-    }
-    if (isset($parameters['max_depth'])) {
-      $query->condition('depth', $parameters['max_depth'], '<=');
-    }
-    // Add custom query conditions, if any were passed.
-    if (isset($parameters['conditions'])) {
-      foreach ($parameters['conditions'] as $column => $value) {
-        $query->condition($column, $value);
-      }
-    }
-
-    // Build an ordered array of links using the query result object.
-    $links = array();
-    if ($result = $query->execute()) {
-      $links = menu_link_load_multiple($result);
-    }
-    $active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array());
-    $data['tree'] = menu_tree_data($links, $active_trail, $min_depth);
-    $data['node_links'] = array();
-    menu_tree_collect_node_links($data['tree'], $data['node_links']);
-
-    // Cache the data, if it is not already in the cache.
-    \Drupal::cache('menu')->set($tree_cid, $data, Cache::PERMANENT, array('menu' => $menu_name));
-    $trees[$tree_cid] = $data;
-  }
-
-  return $trees[$tree_cid];
-}
-
-/**
- * Collects node links from a given menu tree recursively.
- *
- * @param $tree
- *   The menu tree you wish to collect node links from.
- * @param $node_links
- *   An array in which to store the collected node links.
- */
-function menu_tree_collect_node_links(&$tree, &$node_links) {
-  foreach ($tree as $key => $v) {
-    if ($tree[$key]['link']['router_path'] == 'node/%') {
-      $nid = substr($tree[$key]['link']['link_path'], 5);
-      if (is_numeric($nid)) {
-        $node_links[$nid][$tree[$key]['link']['mlid']] = &$tree[$key]['link'];
-        $tree[$key]['link']['access'] = FALSE;
-      }
-    }
-    if ($tree[$key]['below']) {
-      menu_tree_collect_node_links($tree[$key]['below'], $node_links);
-    }
-  }
-}
-
-/**
- * Checks access and performs dynamic operations for each link in the tree.
- *
- * @param $tree
- *   The menu tree you wish to operate on.
- * @param $node_links
- *   A collection of node link references generated from $tree by
- *   menu_tree_collect_node_links().
- */
-function menu_tree_check_access(&$tree, $node_links = array()) {
-  if ($node_links) {
-    $nids = array_keys($node_links);
-    $select = db_select('node_field_data', 'n');
-    $select->addField('n', 'nid');
-    // @todo This should be actually filtering on the desired node status field
-    //   language and just fall back to the default language.
-    $select->condition('n.status', 1);
-
-    $select->condition('n.nid', $nids, 'IN');
-    $select->addTag('node_access');
-    $nids = $select->execute()->fetchCol();
-    foreach ($nids as $nid) {
-      foreach ($node_links[$nid] as $mlid => $link) {
-        $node_links[$nid][$mlid]['access'] = TRUE;
-      }
-    }
-  }
-  _menu_tree_check_access($tree);
-}
-
-/**
- * Sorts the menu tree and recursively checks access for each item.
- */
-function _menu_tree_check_access(&$tree) {
-  $new_tree = array();
-  foreach ($tree as $key => $v) {
-    $item = &$tree[$key]['link'];
-    _menu_link_translate($item);
-    if ($item['access'] || ($item['in_active_trail'] && strpos($item['href'], '%') !== FALSE)) {
-      if ($tree[$key]['below']) {
-        _menu_tree_check_access($tree[$key]['below']);
-      }
-      // The weights are made a uniform 5 digits by adding 50000 as an offset.
-      // After _menu_link_translate(), $item['title'] has the localized link title.
-      // Adding the mlid to the end of the index insures that it is unique.
-      $new_tree[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $tree[$key];
-    }
-  }
-  // Sort siblings in the tree based on the weights and localized titles.
-  ksort($new_tree);
-  $tree = $new_tree;
-}
-
-/**
- * Sorts and returns the built data representing a menu tree.
- *
- * @param $links
- *   A flat array of menu links that are part of the menu. Each array element
- *   is an associative array of information about the menu link, containing the
- *   fields from the {menu_links} table, and optionally additional information
- *   from the {menu_router} table, if the menu item appears in both tables.
- *   This array must be ordered depth-first. See _menu_build_tree() for a sample
- *   query.
- * @param $parents
- *   An array of the menu link ID values that are in the path from the current
- *   page to the root of the menu tree.
- * @param $depth
- *   The minimum depth to include in the returned menu tree.
- *
- * @return
- *   An array of menu links in the form of a tree. Each item in the tree is an
- *   associative array containing:
- *   - link: The menu link item from $links, with additional element
- *     'in_active_trail' (TRUE if the link ID was in $parents).
- *   - below: An array containing the sub-tree of this item, where each element
- *     is a tree item array with 'link' and 'below' elements. This array will be
- *     empty if the menu item has no items in its sub-tree having a depth
- *     greater than or equal to $depth.
- */
-function menu_tree_data(array $links, array $parents = array(), $depth = 1) {
-  // Reverse the array so we can use the more efficient array_pop() function.
-  $links = array_reverse($links);
-  return _menu_tree_data($links, $parents, $depth);
-}
-
-/**
- * Builds the data representing a menu tree.
- *
- * The function is a bit complex because the rendering of a link depends on
- * the next menu link.
- */
-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.
-    $item['in_active_trail'] = in_array($item['mlid'], $parents);
-    // Add the current link to the tree.
-    $tree[$item['mlid']] = array(
-      'link' => $item,
-      'below' => array(),
-    );
-    // Look ahead to the next link, but leave it on the array so it's available
-    // to other recursive function calls if we return or build a sub-tree.
-    $next = end($links);
-    // Check whether the next link is the first in a new sub-tree.
-    if ($next && $next['depth'] > $depth) {
-      // Recursively call _menu_tree_data to build the sub-tree.
-      $tree[$item['mlid']]['below'] = _menu_tree_data($links, $parents, $next['depth']);
-      // Fetch next link after filling the sub-tree.
-      $next = end($links);
-    }
-    // Determine if we should exit the loop and return.
-    if (!$next || $next['depth'] < $depth) {
-      break;
-    }
-  }
-  return $tree;
-}
-
-/**
  * Implements template_preprocess_HOOK() for theme_menu_tree().
  */
 function template_preprocess_menu_tree(&$variables) {
@@ -1112,7 +524,9 @@ function menu_navigation_links($menu_name, $level = 0) {
   }
 
   // Get the menu hierarchy for the current page.
-  $tree = menu_tree_page_data($menu_name, $level + 1);
+  /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
+  $menu_tree = \Drupal::service('menu_link.tree');
+  $tree = $menu_tree->buildPageData($menu_name, $level + 1);
 
   // Go down the active trail until the right level is reached.
   while ($level-- > 0 && $tree) {
@@ -1361,6 +775,9 @@ function menu_set_active_item($path) {
 function menu_set_active_trail($new_trail = NULL) {
   $trail = &drupal_static(__FUNCTION__);
 
+  /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
+  $menu_tree = \Drupal::service('menu_link.tree');
+
   if (isset($new_trail)) {
     $trail = $new_trail;
   }
@@ -1384,7 +801,7 @@ function menu_set_active_trail($new_trail = NULL) {
       // Pass TRUE for $only_active_trail to make menu_tree_page_data() build
       // a stripped down menu tree containing the active trail only, in case
       // the given menu has not been built in this request yet.
-      $tree = menu_tree_page_data($preferred_link['menu_name'], NULL, TRUE);
+      $tree = $menu_tree->buildPageData($preferred_link['menu_name'], NULL, TRUE);
       list($key, $curr) = each($tree);
     }
     // There is no link for the current path.
diff --git a/core/modules/menu/lib/Drupal/menu/MenuFormController.php b/core/modules/menu/lib/Drupal/menu/MenuFormController.php
index d6720a7..fd3f123 100644
--- a/core/modules/menu/lib/Drupal/menu/MenuFormController.php
+++ b/core/modules/menu/lib/Drupal/menu/MenuFormController.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Core\Language\Language;
 use Drupal\menu_link\MenuLinkStorageControllerInterface;
+use Drupal\menu_link\MenuTreeInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -34,6 +35,13 @@ class MenuFormController extends EntityFormController {
   protected $menuLinkStorage;
 
   /**
+   * The menu tree service.
+   *
+   * @var \Drupal\menu_link\MenuTreeInterface
+   */
+  protected $menuTree;
+
+  /**
    * The overview tree form.
    *
    * @var array
@@ -47,10 +55,13 @@ class MenuFormController extends EntityFormController {
    *   The factory for entity queries.
    * @param \Drupal\menu_link\MenuLinkStorageControllerInterface $menu_link_storage
    *   The menu link storage controller.
+   * @param \Drupal\menu_link\MenuTreeInterface $menu_tree
+   *   The menu tree service.
    */
-  public function __construct(QueryFactory $entity_query_factory, MenuLinkStorageControllerInterface $menu_link_storage) {
+  public function __construct(QueryFactory $entity_query_factory, MenuLinkStorageControllerInterface $menu_link_storage, MenuTreeInterface $menu_tree) {
     $this->entityQueryFactory = $entity_query_factory;
     $this->menuLinkStorage = $menu_link_storage;
+    $this->menuTree = $menu_tree;
   }
 
   /**
@@ -59,7 +70,8 @@ public function __construct(QueryFactory $entity_query_factory, MenuLinkStorageC
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('entity.query'),
-      $container->get('entity.manager')->getStorageController('menu_link')
+      $container->get('entity.manager')->getStorageController('menu_link'),
+      $container->get('menu_link.tree')
     );
   }
 
@@ -256,13 +268,9 @@ protected function buildOverviewForm(array &$form, array &$form_state) {
     }
 
     $delta = max(count($links), 50);
-    $tree = menu_tree_data($links);
-    $node_links = array();
-    menu_tree_collect_node_links($tree, $node_links);
-
     // We indicate that a menu administrator is running the menu access check.
     $this->getRequest()->attributes->set('_menu_admin', TRUE);
-    menu_tree_check_access($tree, $node_links);
+    $tree = $this->menuTree->buildTreeData($links);
     $this->getRequest()->attributes->set('_menu_admin', FALSE);
 
     $form = array_merge($form, $this->buildOverviewTreeForm($tree, $delta));
diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index 8eb1d34..72bc84f 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -263,10 +263,13 @@ function _menu_get_options($menus, $available_menus, $item) {
     $limit = _menu_parent_depth_limit($item);
   }
 
+  /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
+  $menu_tree = \Drupal::service('menu_link.tree');
+
   $options = array();
   foreach ($menus as $menu_name => $title) {
     if (isset($available_menus[$menu_name])) {
-      $tree = menu_tree_all_data($menu_name, NULL);
+      $tree = $menu_tree->buildAllData($menu_name, NULL);
       $options[$menu_name . ':0'] = '<' . $title . '>';
       _menu_parents_recurse($tree, $menu_name, '--', $options, $item['mlid'], $limit);
     }
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php
new file mode 100644
index 0000000..a4d5d7e
--- /dev/null
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php
@@ -0,0 +1,670 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link\MenuTree.
+ */
+
+namespace Drupal\menu_link;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\Query\QueryFactory;
+use Drupal\Core\KeyValueStore\StateInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Provides the default implementation of a menu tree.
+ */
+class MenuTree implements MenuTreeInterface {
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   *   The database connection.
+   */
+  protected $database;
+
+  /**
+   * The cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cache;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languagerManager;
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * The menu link storage controller.
+   *
+   * @var \Drupal\menu_link\MenuLinkStorageControllerInterface
+   */
+  protected $menuLinkStorage;
+
+  /**
+   * The entity query factory.
+   *
+   * @var \Drupal\Core\Entity\Query\QueryFactory
+   */
+  protected $queryFactory;
+
+  /**
+   * The state.
+   *
+   * @var \Drupal\Core\KeyValueStore\StateInterface
+   */
+  protected $state;
+
+  /**
+   * A list of active trail paths keyed by $menu_name.
+   *
+   * @var array
+   */
+  protected $trailPaths;
+
+  /**
+   * Stores the rendered menu output keyed by $menu_name.
+   *
+   * @var array
+   */
+  protected $menuOutput;
+
+  /**
+   * Stores the menu menu tree keyed by a cache ID.
+   *
+   * This cache ID is built using the $menu_name, the current language and
+   * some parameters passed into an entity query.
+   */
+  protected $menuTree;
+
+  /**
+   * Stores the full menu tree data keyed by a cache ID.
+   *
+   * This cache ID is built with the menu name, a passed in root link ID, the
+   * current language as well as the maximum depth.
+   *
+   * @var array
+   */
+  protected $menuFullTrees;
+
+  /**
+   * Stores the menu tree data on the current page keyed by a cache ID.
+   *
+   * This contains less information than a tree built with buildAllData.
+   *
+   * @var array
+   */
+  protected $menuPageTrees;
+
+  /**
+   * Constructs a new MenuTree.
+   *
+   * @param \Drupal\Core\Database\Connection $database
+   *   The database connection.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   The cache backend.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query_factory
+   *   The entity query factory.
+   * @param \Drupal\Core\KeyValueStore\StateInterface $state
+   *   The state.
+   */
+  public function __construct(Connection $database, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, RequestStack $request_stack, EntityManagerInterface $entity_manager, QueryFactory $entity_query_factory, StateInterface $state) {
+    $this->database = $database;
+    $this->cache = $cache_backend;
+    $this->languageManager = $language_manager;
+    $this->requestStack = $request_stack;
+    $this->menuLinkStorage = $entity_manager->getStorageController('menu_link');
+    $this->queryFactory = $entity_query_factory;
+    $this->state = $state;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildAllData($menu_name, $link = NULL, $max_depth = NULL) {
+    $language_interface = $this->languageManager->getCurrentLanguage();
+
+    // Use $mlid as a flag for whether the data being loaded is for the whole
+    // tree.
+    $mlid = isset($link['mlid']) ? $link['mlid'] : 0;
+    // Generate a cache ID (cid) specific for this $menu_name, $link, $language,
+    // and depth.
+    $cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $language_interface->id . ':' . (int) $max_depth;
+
+    if (!isset($this->menuFullTrees[$cid])) {
+      // If the static variable doesn't have the data, check {cache_menu}.
+      $cache = $this->cache->get($cid);
+      if ($cache && isset($cache->data)) {
+        // If the cache entry exists, it contains the parameters for
+        // menu_build_tree().
+        $tree_parameters = $cache->data;
+      }
+      // If the tree data was not in the cache, build $tree_parameters.
+      if (!isset($tree_parameters)) {
+        $tree_parameters = array(
+          'min_depth' => 1,
+          'max_depth' => $max_depth,
+        );
+        if ($mlid) {
+          // The tree is for a single item, so we need to match the values in
+          // its p columns and 0 (the top level) with the plid values of other
+          // links.
+          $parents = array(0);
+          for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
+            if (!empty($link["p$i"])) {
+              $parents[] = $link["p$i"];
+            }
+          }
+          $tree_parameters['expanded'] = $parents;
+          $tree_parameters['active_trail'] = $parents;
+          $tree_parameters['active_trail'][] = $mlid;
+        }
+
+        // Cache the tree building parameters using the page-specific cid.
+        $this->cache->set($cid, $tree_parameters, Cache::PERMANENT, array('menu' => $menu_name));
+      }
+
+      // Build the tree using the parameters; the resulting tree will be cached
+      // by _menu_build_tree()).
+      $this->menuFullTrees[$cid] = $this->buildTree($menu_name, $tree_parameters);
+    }
+
+    return $this->menuFullTrees[$cid];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildPageData($menu_name, $max_depth = NULL, $only_active_trail = FALSE) {
+    $language_interface = $this->languageManager->getCurrentLanguage();
+
+    // Check if the active trail has been overridden for this menu tree.
+    $active_path = $this->getPath($menu_name);
+    // Load the request corresponding to the current page.
+    $request = $this->requestStack->getCurrentRequest();
+    $system_path = NULL;
+    if ($route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME)) {
+      // @todo https://drupal.org/node/2068471 is adding support so we can tell
+      // if this is called on a 404/403 page.
+      $system_path = $request->attributes->get('_system_path');
+      $page_not_403 = 1;
+    }
+    if (isset($system_path)) {
+      if (isset($max_depth)) {
+        $max_depth = min($max_depth, MENU_MAX_DEPTH);
+      }
+      // Generate a cache ID (cid) specific for this page.
+      $cid = 'links:' . $menu_name . ':page:' . $system_path . ':' . $language_interface->id . ':' . $page_not_403 . ':' . (int) $max_depth;
+      // If we are asked for the active trail only, and $menu_name has not been
+      // built and cached for this page yet, then this likely means that it
+      // won't be built anymore, as this function is invoked from
+      // 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 active trail only.
+      // @see menu_set_active_trail()
+      if (!isset($this->menuPageTrees[$cid]) && $only_active_trail) {
+        $cid .= ':trail';
+      }
+
+      if (!isset($this->menuPageTrees[$cid])) {
+        // If the static variable doesn't have the data, check {cache_menu}.
+        $cache = $this->cache->get($cid);
+        if ($cache && isset($cache->data)) {
+          // If the cache entry exists, it contains the parameters for
+          // menu_build_tree().
+          $tree_parameters = $cache->data;
+        }
+        // If the tree data was not in the cache, build $tree_parameters.
+        if (!isset($tree_parameters)) {
+          $tree_parameters = array(
+            'min_depth' => 1,
+            'max_depth' => $max_depth,
+          );
+          // Parent mlids; used both as key and value to ensure uniqueness.
+          // We always want all the top-level links with plid == 0.
+          $active_trail = array(0 => 0);
+
+          // If this page is accessible to the current user, build the tree
+          // parameters accordingly.
+          if ($page_not_403) {
+            // Find a menu link corresponding to the current path. If
+            // $active_path is NULL, let menu_link_get_preferred() determine
+            // the path.
+            if ($active_link = $this->menuLinkGetPreferred($menu_name, $active_path)) {
+              // The active link may only be taken into account to build the
+              // active trail, if it resides in the requested menu.
+              // Otherwise, we'd needlessly re-run _menu_build_tree() queries
+              // for every menu on every page.
+              if ($active_link['menu_name'] == $menu_name) {
+                // Use all the coordinates, except the last one because
+                // there can be no child beyond the last column.
+                for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
+                  if ($active_link['p' . $i]) {
+                    $active_trail[$active_link['p' . $i]] = $active_link['p' . $i];
+                  }
+                }
+                // If we are asked to build links for the active trail only,skip
+                // the entire 'expanded' handling.
+                if ($only_active_trail) {
+                  $tree_parameters['only_active_trail'] = TRUE;
+                }
+              }
+            }
+            $parents = $active_trail;
+
+            $expanded = $this->state->get('menu_expanded');
+            // Check whether the current menu has any links set to be expanded.
+            if (!$only_active_trail && $expanded && in_array($menu_name, $expanded)) {
+              // Collect all the links set to be expanded, and then add all of
+              // their children to the list as well.
+              do {
+                $query = $this->queryFactory->get('menu_link')
+                  ->condition('menu_name', $menu_name)
+                  ->condition('expanded', 1)
+                  ->condition('has_children', 1)
+                  ->condition('plid', $parents, 'IN')
+                  ->condition('mlid', $parents, 'NOT IN');
+                $result = $query->execute();
+                $parents += $result;
+              } while (!empty($result));
+            }
+            $tree_parameters['expanded'] = $parents;
+            $tree_parameters['active_trail'] = $active_trail;
+          }
+          // If access is denied, we only show top-level links in menus.
+          else {
+            $tree_parameters['expanded'] = $active_trail;
+            $tree_parameters['active_trail'] = $active_trail;
+          }
+          // Cache the tree building parameters using the page-specific cid.
+          $this->cache->set($cid, $tree_parameters, Cache::PERMANENT, array('menu' => $menu_name));
+        }
+
+        // Build the tree using the parameters; the resulting tree will be
+        // cached by $tihs->buildTree().
+        $this->menuPageTrees[$cid] = $this->buildTree($menu_name, $tree_parameters);
+      }
+      return $this->menuPageTrees[$cid];
+    }
+
+    return array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setPath($menu_name, $path = NULL) {
+    if (isset($path)) {
+      $this->trailPaths[$menu_name] = $path;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPath($menu_name) {
+    return isset($this->trailPaths[$menu_name]) ? $this->trailPaths[$menu_name] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function renderMenu($menu_name) {
+
+    if (!isset($this->menuOutput[$menu_name])) {
+      $tree = $this->buildPageData($menu_name);
+      $this->menuOutput[$menu_name] = $this->renderTree($tree);
+    }
+    return $this->menuOutput[$menu_name];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function renderTree($tree) {
+    $build = array();
+    $items = array();
+
+    // Pull out just the menu links we are going to render so that we
+    // get an accurate count for the first/last classes.
+    foreach ($tree as $data) {
+      if ($data['link']['access'] && !$data['link']['hidden']) {
+        $items[] = $data;
+      }
+    }
+
+    foreach ($items as $data) {
+      $class = array();
+      // Set a class for the <li>-tag. Since $data['below'] may contain local
+      // tasks, only set 'expanded' class if the link also has children within
+      // the current menu.
+      if ($data['link']['has_children'] && $data['below']) {
+        $class[] = 'expanded';
+      }
+      elseif ($data['link']['has_children']) {
+        $class[] = 'collapsed';
+      }
+      else {
+        $class[] = 'leaf';
+      }
+      // Set a class if the link is in the active trail.
+      if ($data['link']['in_active_trail']) {
+        $class[] = 'active-trail';
+        $data['link']['localized_options']['attributes']['class'][] = 'active-trail';
+      }
+
+      // Allow menu-specific theme overrides.
+      $element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
+      $element['#attributes']['class'] = $class;
+      $element['#title'] = $data['link']['title'];
+      // @todo Use route name and parameters to generate the link path, unless
+      //    it is external.
+      $element['#href'] = $data['link']['link_path'];
+      $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array();
+      $element['#below'] = $data['below'] ? $this->renderTree($data['below']) : $data['below'];
+      $element['#original_link'] = $data['link'];
+      // Index using the link's unique mlid.
+      $build[$data['link']['mlid']] = $element;
+    }
+    if ($build) {
+      // Make sure drupal_render() does not re-order the links.
+      $build['#sorted'] = TRUE;
+      // Add the theme wrapper for outer markup.
+      // Allow menu-specific theme overrides.
+      $build['#theme_wrappers'][] = 'menu_tree__' . strtr($data['link']['menu_name'], '-', '_');
+      // Set cache tag.
+      $menu_name = $data['link']['menu_name'];
+      $build['#cache']['tags']['menu'][$menu_name] = $menu_name;
+    }
+
+    return $build;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildTree($menu_name, array $parameters = array()) {
+    // Build the menu tree.
+    $data = $this->doBuildTree($menu_name, $parameters);
+    // Check access for the current user to each item in the tree.
+    $this->checkAccess($data['tree'], $data['node_links']);
+    return $data['tree'];
+  }
+
+  /**
+   * Builds a menu tree.
+   *
+   * This function may be used build the data for a menu tree only, for example
+   * to further massage the data manually before further processing happens.
+   * menu_tree_check_access() needs to be invoked afterwards.
+   *
+   * @param string $menu_name
+   *   The name of the menu.
+   * @param array $parameters
+   *   The parameters passed into static::buildTree()
+   *
+   * @see static::buildTree()
+   */
+  protected function doBuildTree($menu_name, array $parameters = array()) {
+    $language_interface = $this->languageManager->getCurrentLanguage();
+
+    // Build the cache id; sort parents to prevent duplicate storage and remove
+    // default parameter values.
+    if (isset($parameters['expanded'])) {
+      sort($parameters['expanded']);
+    }
+    $tree_cid = 'links:' . $menu_name . ':tree-data:' . $language_interface->id . ':' . hash('sha256', serialize($parameters));
+
+    // If we do not have this tree in the static cache, check {cache_menu}.
+    if (!isset($this->menuTree[$tree_cid])) {
+      $cache = $this->cache->get($tree_cid);
+      if ($cache && isset($cache->data)) {
+        $this->menuFullTrees[$tree_cid] = $cache->data;
+      }
+    }
+
+    if (!isset($this->menuTree[$tree_cid])) {
+      $query = $this->queryFactory->get('menu_link');
+      for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
+        $query->sort('p' . $i, 'ASC');
+      }
+      $query->condition('menu_name', $menu_name);
+      if (!empty($parameters['expanded'])) {
+        $query->condition('plid', $parameters['expanded'], 'IN');
+      }
+      elseif (!empty($parameters['only_active_trail'])) {
+        $query->condition('mlid', $parameters['active_trail'], 'IN');
+      }
+      $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1);
+      if ($min_depth != 1) {
+        $query->condition('depth', $min_depth, '>=');
+      }
+      if (isset($parameters['max_depth'])) {
+        $query->condition('depth', $parameters['max_depth'], '<=');
+      }
+      // Add custom query conditions, if any were passed.
+      if (isset($parameters['conditions'])) {
+        foreach ($parameters['conditions'] as $column => $value) {
+          $query->condition($column, $value);
+        }
+      }
+
+      // Build an ordered array of links using the query result object.
+      $links = array();
+      if ($result = $query->execute()) {
+        $links = $this->menuLinkStorage->loadMultiple($result);
+      }
+      $active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array());
+      $data['tree'] = $this->buildTreeData($links, $active_trail, $min_depth);
+      $data['node_links'] = array();
+      $this->collectNodeLinks($data['tree'], $data['node_links']);
+
+      // Cache the data, if it is not already in the cache.
+      $this->cache->set($tree_cid, $data, Cache::PERMANENT, array('menu' => $menu_name));
+      $this->menuTree[$tree_cid] = $data;
+    }
+
+    return $this->menuTree[$tree_cid];
+  }
+
+  /**
+   * Collects node links from a given menu tree recursively.
+   *
+   * @param array $tree
+   *   The menu tree you wish to collect node links from.
+   * @param array $node_links
+   *   An array in which to store the collected node links.
+   */
+  protected function collectNodeLinks(&$tree, &$node_links) {
+    foreach ($tree as $key => $v) {
+      if ($tree[$key]['link']['route_name'] == 'node.view') {
+        $nid = $tree[$key]['link']['route_parameters']['node'];
+        if (is_numeric($nid)) {
+          $node_links[$nid][$tree[$key]['link']['mlid']] = &$tree[$key]['link'];
+          $tree[$key]['link']['access'] = FALSE;
+        }
+      }
+      if ($tree[$key]['below']) {
+        $this->collectNodeLinks($tree[$key]['below'], $node_links);
+      }
+    }
+  }
+
+  /**
+   * Checks access and performs dynamic operations for each link in the tree.
+   *
+   * @param array $tree
+   *   The menu tree you wish to operate on.
+   * @param array $node_links
+   *   A collection of node link references generated from $tree by
+   *   menu_tree_collect_node_links().
+   */
+  protected function checkAccess(&$tree, $node_links = array()) {
+    if ($node_links) {
+      $this->checkNodeLinksAccess($node_links);
+    }
+    $this->doCheckAccess($tree);
+  }
+
+  /**
+   * Checks access on  node links using the node_access query tag.
+   *
+   * @param array $node_links
+   *   An array of node links keyed by NID.
+   *
+   * @return mixed
+   *   The node links.
+   */
+  protected function checkNodeLinksAccess(array $node_links) {
+    $nids = array_keys($node_links);
+    $select = $this->database->select('node_field_data', 'n');
+    $select->addField('n', 'nid');
+    // @todo This should be actually filtering on the desired node status field
+    //   language and just fall back to the default language.
+    $select->condition('n.status', 1);
+
+    $select->condition('n.nid', $nids, 'IN');
+    $select->addTag('node_access');
+    $nids = $select->execute()->fetchCol();
+    foreach ($nids as $nid) {
+      foreach ($node_links[$nid] as $mlid => $link) {
+        $node_links[$nid][$mlid]['access'] = TRUE;
+      }
+    }
+    return $node_links;
+  }
+
+  /**
+   * Sorts the menu tree and recursively checks access for each item.
+   *
+   * @param array $tree
+   *   The menu tree you wish to operate on.
+   */
+  protected function doCheckAccess(&$tree) {
+    $new_tree = array();
+    foreach ($tree as $key => $v) {
+      $item = &$tree[$key]['link'];
+      $this->menuLinkTranslate($item);
+      if ($item['access'] || ($item['in_active_trail'] && strpos($item['href'], '%') !== FALSE)) {
+        if ($tree[$key]['below']) {
+          $this->doCheckAccess($tree[$key]['below']);
+        }
+        // The weights are made a uniform 5 digits by adding 50000 as an offset.
+        // After _menu_link_translate(), $item['title'] has the localized link
+        // title. Adding the mlid to the end of the index insures that it is
+        // unique.
+        $new_tree[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $tree[$key];
+      }
+    }
+    // Sort siblings in the tree based on the weights and localized titles.
+    ksort($new_tree);
+    $tree = $new_tree;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildTreeData(array $links, array $parents = array(), $depth = 1) {
+    $data['tree'] = $this->doBuildTreeData($links, $parents, $depth);
+    $data['node_links'] = array();
+    $this->collectNodeLinks($data['tree'], $data['node_links']);
+    $this->checkAccess($data['tree'], $data['node_links']);
+    return $data['tree'];
+  }
+
+  protected function doBuildTreeData(array $links, array $parents = array(), $depth = 1) {
+    // Reverse the array so we can use the more efficient array_pop() function.
+    $links = array_reverse($links);
+    return $this->treeDataRecursive($links, $parents, $depth);
+  }
+
+  /**
+   * Builds the data representing a menu tree.
+   *
+   * The function is a bit complex because the rendering of a link depends on
+   * the next menu link.
+   *
+   * @param array $links
+   *   A flat array of menu links that are part of the menu. Each array element
+   *   is an associative array of information about the menu link, containing the
+   *   fields from the {menu_links} table, and optionally additional information
+   *   from the {menu_router} table, if the menu item appears in both tables.
+   *   This array must be ordered depth-first. See _menu_build_tree() for a sample
+   *   query.
+   * @param array $parents
+   *   An array of the menu link ID values that are in the path from the current
+   *   page to the root of the menu tree.
+   * @param int $depth
+   *   The minimum depth to include in the returned menu tree.
+   *
+   * @return array
+   */
+  protected function treeDataRecursive(&$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.
+      $item['in_active_trail'] = in_array($item['mlid'], $parents);
+      // Add the current link to the tree.
+      $tree[$item['mlid']] = array(
+        'link' => $item,
+        'below' => array(),
+      );
+      // Look ahead to the next link, but leave it on the array so it's
+      // available to other recursive function calls if we return or build a
+      // sub-tree.
+      $next = end($links);
+      // Check whether the next link is the first in a new sub-tree.
+      if ($next && $next['depth'] > $depth) {
+        // Recursively call doBuildTreeData to build the sub-tree.
+        $tree[$item['mlid']]['below'] = $this->treeDataRecursive($links, $parents, $next['depth']);
+        // Fetch next link after filling the sub-tree.
+        $next = end($links);
+      }
+      // Determine if we should exit the loop and return.
+      if (!$next || $next['depth'] < $depth) {
+        break;
+      }
+    }
+    return $tree;
+  }
+
+  /**
+   * Wraps menu_link_get_preferred().
+   */
+  protected function menuLinkGetPreferred($menu_name, $active_path) {
+    return menu_link_get_preferred($active_path, $menu_name);
+  }
+
+  /**
+   * Wraps _menu_link_translate().
+   */
+  protected function menuLinkTranslate(&$item) {
+    _menu_link_translate($item);
+  }
+
+}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeInterface.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeInterface.php
new file mode 100644
index 0000000..0393e11
--- /dev/null
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeInterface.php
@@ -0,0 +1,174 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link\MenuTreeInterface.
+ */
+
+namespace Drupal\menu_link;
+
+/**
+ * Defines an interface for trees out of menu links.
+ */
+interface MenuTreeInterface {
+
+  /**
+   * Returns a rendered menu tree.
+   *
+   * The menu item's LI element is given one of the following classes:
+   * - expanded: The menu item is showing its submenu.
+   * - collapsed: The menu item has a submenu which is not shown.
+   * - leaf: The menu item has no submenu.
+   *
+   * @param array $tree
+   *   A data structure representing the tree as returned from menu_tree_data.
+   *
+   * @return array
+   *   A structured array to be rendered by drupal_render().
+   */
+  public function renderTree($tree);
+
+  /**
+   * Sets the path for determining the active trail of the specified menu tree.
+   *
+   * This path will also affect the breadcrumbs under some circumstances.
+   * Breadcrumbs are built using the preferred link returned by
+   * menu_link_get_preferred(). If the preferred link is inside one of the menus
+   * specified in calls to menu_tree_set_path(), the preferred link will be
+   * overridden by the corresponding path returned by menu_tree_get_path().
+   *
+   * Setting this path does not affect the main content; for that use
+   * menu_set_active_item() instead.
+   *
+   * @param string $menu_name
+   *   The name of the affected menu tree.
+   * @param string $path
+   *   The path to use when finding the active trail.
+   */
+  public function setPath($menu_name, $path = NULL);
+
+  /**
+   * Gets the path for determining the active trail of the specified menu tree.
+   *
+   * @param string $menu_name
+   *   The menu name of the requested tree.
+   *
+   * @return string
+   *   A string containing the path. If no path has been specified with
+   *   static::setPath(), NULL is returned.
+   */
+  public function getPath($menu_name);
+
+  /**
+   * Sorts and returns the built data representing a menu tree.
+   *
+   * @param array $links
+   *   A flat array of menu links that are part of the menu. Each array element
+   *   is an associative array of information about the menu link, containing the
+   *   fields from the {menu_links} table, and optionally additional information
+   *   from the {menu_router} table, if the menu item appears in both tables.
+   *   This array must be ordered depth-first. See _menu_build_tree() for a sample
+   *   query.
+   * @param array $parents
+   *   An array of the menu link ID values that are in the path from the current
+   *   page to the root of the menu tree.
+   * @param int $depth
+   *   The minimum depth to include in the returned menu tree.
+   *
+   * @return array
+   *   An array of menu links in the form of a tree. Each item in the tree is an
+   *   associative array containing:
+   *   - link: The menu link item from $links, with additional element
+   *     'in_active_trail' (TRUE if the link ID was in $parents).
+   *   - below: An array containing the sub-tree of this item, where each element
+   *     is a tree item array with 'link' and 'below' elements. This array will be
+   *     empty if the menu item has no items in its sub-tree having a depth
+   *     greater than or equal to $depth.
+   */
+  public function buildTreeData(array $links, array $parents = array(), $depth = 1);
+
+  /**
+   * Gets the data structure for a named menu tree, based on the current page.
+   *
+   * The tree order is maintained by storing each parent in an individual
+   * field, see http://drupal.org/node/141866 for more.
+   *
+   * @param string $menu_name
+   *   The named menu links to return.
+   * @param int $max_depth
+   *   (optional) The maximum depth of links to retrieve.
+   * @param bool $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.
+   *
+   * @return array
+   *   An array of menu links, in the order they should be rendered. The array
+   *   is a list of associative arrays -- these have two keys, link and below.
+   *   link is a menu item, ready for theming as a link. Below represents the
+   *   submenu below the link if there is one, and it is a subtree that has the
+   *   same structure described for the top-level array.
+   */
+  public function buildPageData($menu_name, $max_depth = NULL, $only_active_trail = FALSE);
+
+  /**
+   * Gets the data structure representing a named menu tree.
+   *
+   * Since this can be the full tree including hidden items, the data returned
+   * may be used for generating an an admin interface or a select.
+   *
+   * @param string $menu_name
+   *   The named menu links to return
+   * @param array $link
+   *   A fully loaded menu link, or NULL. If a link is supplied, only the
+   *   path to root will be included in the returned tree - as if this link
+   *   represented the current page in a visible menu.
+   * @param int $max_depth
+   *   Optional maximum depth of links to retrieve. Typically useful if only one
+   *   or two levels of a sub tree are needed in conjunction with a non-NULL
+   *   $link, in which case $max_depth should be greater than $link['depth'].
+   *
+   * @return array
+   *   An tree of menu links in an array, in the order they should be rendered.
+   */
+  public function buildAllData($menu_name, $link = NULL, $max_depth = NULL);
+
+  /**
+   * Renders a menu tree based on the current path.
+   *
+   * @param string $menu_name
+   *   The name of the menu.
+   *
+   * @return array
+   *   A structured array representing the specified menu on the current page, to
+   *   be rendered by drupal_render().
+   */
+  public function renderMenu($menu_name);
+
+  /**
+   * Builds a menu tree, translates links, and checks access.
+   *
+   * @param string $menu_name
+   *   The name of the menu.
+   * @param array $parameters
+   *   (optional) An associative array of build parameters. Possible keys:
+   *   - expanded: An array of parent link ids to return only menu links that are
+   *     children of one of the plids in this list. If empty, the whole menu tree
+   *     is built, unless 'only_active_trail' is TRUE.
+   *   - 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.
+   *   - 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).
+   *   - max_depth: The maximum depth of menu links in the resulting tree.
+   *   - conditions: An associative array of custom database select query
+   *     condition key/value pairs; see _menu_build_tree() for the actual query.
+   *
+   * @return array
+   *   A fully built menu tree.
+   */
+  public function buildTree($menu_name, array $parameters = array());
+
+}
diff --git a/core/modules/menu_link/menu_link.services.yml b/core/modules/menu_link/menu_link.services.yml
new file mode 100644
index 0000000..8a2c5b2
--- /dev/null
+++ b/core/modules/menu_link/menu_link.services.yml
@@ -0,0 +1,4 @@
+services:
+  menu_link.tree:
+    class: Drupal\menu_link\MenuTree
+    arguments: ['@database', '@cache.menu', '@language_manager', '@request_stack', '@entity.manager', '@entity.query', '@state']
diff --git a/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php b/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php
new file mode 100644
index 0000000..162dead
--- /dev/null
+++ b/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php
@@ -0,0 +1,389 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link\Tests\MenuTreeTest.
+ */
+
+namespace Drupal\menu_link\Tests;
+
+use Drupal\menu_link\MenuTree;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Tests the menu tree.
+ *
+ * @group Drupal
+ * @group menu_link
+ *
+ * @coversDefaultClass \Drupal\menu_link\MenuTree
+ */
+class MenuTreeTest extends UnitTestCase {
+
+  /**
+   * The tested menu tree.
+   *
+   * @var \Drupal\menu_link\MenuTree|\Drupal\menu_link\Tests\TestMenuTree
+   */
+  protected $menuTree;
+
+  /**
+   * The mocked database connection.
+   *
+   * @var \Drupal\Core\DatabaseConnection|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $connection;
+
+  /**
+   * The mocked cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cacheBackend;
+
+  /**
+   * The mocked language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $languageManager;
+
+  /**
+   * The test request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack.
+   */
+  protected $requestStack;
+
+  /**
+   * The mocked entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entityManager;
+
+  /**
+   * The mocked entity query factor.y
+   *
+   * @var  \Drupal\Core\Entity\Query\QueryFactory|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entityQueryFactory;
+
+  /**
+   * The mocked state.
+   *
+   * @var \Drupal\Core\KeyValueStore\StateInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $state;
+
+  /**
+   * Stores some default values for a menu link.
+   *
+   * @var array
+   */
+  protected $defaultMenuLink = array(
+    'menu_name' => 'main-menu',
+    'mlid' => 1,
+    'title' => 'Example 1',
+    'route_name' => 'example1',
+    'link_path' => 'example1',
+    'access' => 1,
+    'hidden' => FALSE,
+    'has_children' => FALSE,
+    'in_active_trail' => TRUE,
+    'localized_options' => array('attributes' => array('title' => '')),
+    'weight' => 0,
+  );
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Tests \Drupal\menu_link\MenuTree',
+      'description' => '',
+      'group' => 'Menu',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
+    $this->requestStack = new RequestStack();
+    $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
+    $this->entityQueryFactory = $this->getMockBuilder('Drupal\Core\Entity\Query\QueryFactory')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $this->state = $this->getMock('Drupal\Core\KeyValueStore\StateInterface');
+
+    $this->menuTree = new TestMenuTree($this->connection, $this->cacheBackend, $this->languageManager, $this->requestStack, $this->entityManager, $this->entityQueryFactory, $this->state);
+  }
+
+  /**
+   * Tests active paths.
+   *
+   * @covers ::setPath
+   * @covers ::getPath
+   */
+  public function testActivePaths() {
+    $this->assertNull($this->menuTree->getPath('test_menu1'));
+
+    $this->menuTree->setPath('test_menu1', 'example_path1');
+    $this->assertEquals('example_path1', $this->menuTree->getPath('test_menu1'));
+    $this->assertNull($this->menuTree->getPath('test_menu2'));
+
+    $this->menuTree->setPath('test_menu2', 'example_path2');
+    $this->assertEquals('example_path1', $this->menuTree->getPath('test_menu1'));
+    $this->assertEquals('example_path2', $this->menuTree->getPath('test_menu2'));
+  }
+
+  /**
+   * Tests buildTreeData with a single level.
+   *
+   * @covers ::buildTreeData
+   * @covers ::doBuildTreeData
+   */
+  public function testBuildTreeDataWithSingleLevel() {
+    $items = array();
+    $items[] = array(
+      'mlid' => 1,
+      'depth' => 1,
+      'route_name' => 'example1',
+    );
+    $items[] = array(
+      'mlid' => 2,
+      'depth' => 1,
+      'route_name' => 'example2',
+    );
+
+    $result = $this->menuTree->buildTreeData($items, array(), 1);
+
+    $this->assertCount(2, $result);
+    $this->assertEquals(array('mlid' => 1, 'depth' => 1, 'route_name' => 'example1', 'in_active_trail' => FALSE), $result[1]['link']);
+    $this->assertEquals(array('mlid' => 2, 'depth' => 1, 'route_name' => 'example2', 'in_active_trail' => FALSE), $result[2]['link']);
+  }
+
+  /**
+   * Tests buildTreeData with a single level and one item being active.
+   *
+   * @covers ::buildTreeData
+   * @covers ::doBuildTreeData
+   */
+  public function testBuildTreeDataWithSingleLevelAndActiveItem() {
+    $items = array();
+    $items[] = array(
+      'mlid' => 1,
+      'depth' => 1,
+      'route_name' => 'example1',
+    );
+    $items[] = array(
+      'mlid' => 2,
+      'depth' => 1,
+      'route_name' => 'example2',
+    );
+
+    $result = $this->menuTree->buildTreeData($items, array(1), 1);
+
+    $this->assertCount(2, $result);
+    $this->assertEquals(array('mlid' => 1, 'depth' => 1, 'route_name' => 'example1', 'in_active_trail' => TRUE), $result[1]['link']);
+    $this->assertEquals(array('mlid' => 2, 'depth' => 1, 'route_name' => 'example2', 'in_active_trail' => FALSE), $result[2]['link']);
+  }
+
+  /**
+   * Tests buildTreeData with a single level and none item being active.
+   *
+   * @covers ::buildTreeData
+   * @covers ::doBuildTreeData
+   */
+  public function testBuildTreeDataWithSingleLevelAndNoActiveItem() {
+    $items = array();
+    $items[] = array(
+      'mlid' => 1,
+      'depth' => 1,
+      'route_name' => 'example1',
+    );
+    $items[] = array(
+      'mlid' => 2,
+      'depth' => 1,
+      'route_name' => 'example2',
+    );
+
+    $result = $this->menuTree->buildTreeData($items, array(3), 1);
+
+    $this->assertCount(2, $result);
+    $this->assertEquals(array('mlid' => 1, 'depth' => 1, 'route_name' => 'example1', 'in_active_trail' => FALSE), $result[1]['link']);
+    $this->assertEquals(array('mlid' => 2, 'depth' => 1, 'route_name' => 'example2', 'in_active_trail' => FALSE), $result[2]['link']);
+  }
+
+  /**
+   * Tests buildTreeData with multiple levels.
+   *
+   * @covers ::buildTreeData
+   * @covers ::doBuildTreeData
+   */
+  public function testBuildTreeDataWithMultipleLevels() {
+    $items = array();
+    $items[] = array(
+      'mlid' => 1,
+      'depth' => 1,
+      'route_name' => 'example1',
+    );
+    $items[] = array(
+      'mlid' => 2,
+      'depth' => 1,
+      'route_name' => 'example2',
+    );
+
+    $result = $this->menuTree->buildTreeData($items, array(3), 1);
+
+    $this->assertCount(2, $result);
+    $this->assertEquals(array('mlid' => 1, 'depth' => 1, 'route_name' => 'example1', 'in_active_trail' => FALSE), $result[1]['link']);
+    $this->assertEquals(array('mlid' => 2, 'depth' => 1, 'route_name' => 'example2', 'in_active_trail' => FALSE), $result[2]['link']);
+  }
+
+  /**
+   * Tests buildTreeData with a more complex example.
+   *
+   * @covers ::buildTreeData
+   * @covers ::doBuildTreeData
+   */
+  public function testBuildTreeWithComplexData() {
+    $items = array(
+      1 => array('mlid' => 1, 'depth' => 1),
+      2 => array('mlid' => 2, 'depth' => 1),
+      3 => array('mlid' => 3, 'depth' => 2),
+      4 => array('mlid' => 4, 'depth' => 3),
+      5 => array('mlid' => 5, 'depth' => 1),
+    );
+
+    $tree = $this->menuTree->buildTreeData($items);
+
+    // Validate that parent items #1, #2, and #5 exist on the root level.
+    $this->assertEquals($items[1]['mlid'], $tree[1]['link']['mlid']);
+    $this->assertEquals($items[2]['mlid'], $tree[2]['link']['mlid']);
+    $this->assertEquals($items[5]['mlid'], $tree[5]['link']['mlid']);
+
+    // Validate that child item #4 exists at the correct location in the hierarchy.
+    $this->assertEquals($items[4]['mlid'], $tree[2]['below'][3]['below'][4]['link']['mlid']);
+  }
+
+  /**
+   * Tests the output with a single level.
+   *
+   * @covers ::output
+   */
+  public function testOutputWithSingleLevel() {
+    $tree = array(
+      '1' => array(
+        'link' => array('mlid' => 1) + $this->defaultMenuLink,
+        'below' => array(),
+      ),
+      '2' => array(
+        'link' => array('mlid' => 2) + $this->defaultMenuLink,
+        'below' => array(),
+      ),
+    );
+
+    $output = $this->menuTree->renderTree($tree);
+
+    // Validate that the - in main-menu is changed into an underscore
+    $this->assertEquals($output['1']['#theme'], 'menu_link__main_menu', 'Hyphen is changed to an underscore on menu_link');
+    $this->assertEquals($output['2']['#theme'], 'menu_link__main_menu', 'Hyphen is changed to an underscore on menu_link');
+    $this->assertEquals($output['#theme_wrappers'][0], 'menu_tree__main_menu', 'Hyphen is changed to an underscore on menu_tree wrapper');
+  }
+
+  /**
+   * Tests the output method with a complex example.
+   *
+   * @covers ::output
+   */
+  public function testOutputWithComplexData() {
+    $tree = array(
+      '1'=> array(
+        'link' => array('mlid' => 1, 'has_children' => 1, 'title' => 'Item 1', 'link_path' => 'a') + $this->defaultMenuLink,
+        'below' => array(
+          '2' => array('link' => array('mlid' => 2, 'title' => 'Item 2', 'link_path' => 'a/b') + $this->defaultMenuLink,
+            'below' => array(
+              '3' => array('link' => array('mlid' => 3, 'title' => 'Item 3', 'in_active_trail' => 0, 'link_path' => 'a/b/c') + $this->defaultMenuLink,
+                'below' => array()),
+              '4' => array('link' => array('mlid' => 4, 'title' => 'Item 4', 'in_active_trail' => 0, 'link_path' => 'a/b/d') + $this->defaultMenuLink,
+                'below' => array())
+            )
+          )
+        )
+      ),
+      '5' => array('link' => array('mlid' => 5, 'hidden' => 1, 'title' => 'Item 5', 'link_path' => 'e') + $this->defaultMenuLink, 'below' => array()),
+      '6' => array('link' => array('mlid' => 6, 'title' => 'Item 6', 'in_active_trail' => 0, 'access' => 0, 'link_path' => 'f') + $this->defaultMenuLink, 'below' => array()),
+      '7' => array('link' => array('mlid' => 7, 'title' => 'Item 7', 'in_active_trail' => 0, 'link_path' => 'g') + $this->defaultMenuLink, 'below' => array())
+    );
+
+    $output = $this->menuTree->renderTree($tree);
+
+    // Looking for child items in the data
+    $this->assertEquals( $output['1']['#below']['2']['#href'], 'a/b', 'Checking the href on a child item');
+    $this->assertTrue(in_array('active-trail', $output['1']['#below']['2']['#attributes']['class']), 'Checking the active trail class');
+    // Validate that the hidden and no access items are missing
+    $this->assertFalse(isset($output['5']), 'Hidden item should be missing');
+    $this->assertFalse(isset($output['6']), 'False access should be missing');
+    // Item 7 is after a couple hidden items. Just to make sure that 5 and 6 are skipped and 7 still included
+    $this->assertTrue(isset($output['7']), 'Item after hidden items is present');
+  }
+
+  /**
+   * Tests menu tree access check with a single level.
+   *
+   * @covers ::checkAccess
+   */
+  public function testCheckAccessWithSingleLevel() {
+    $items = array(
+      array('mlid' => 1, 'route_name' => 'menu_test_1', 'depth' => 1, 'link_path' => 'menu_test/test_1', 'in_active_trail' => FALSE) + $this->defaultMenuLink,
+      array('mlid' => 2, 'route_name' => 'menu_test_2', 'depth' => 1, 'link_path' => 'menu_test/test_2', 'in_active_trail' => FALSE) + $this->defaultMenuLink,
+    );
+
+    // Register a menuLinkTranslate to mock the access.
+    $this->menuTree->menuLinkTranslateCallable = function(&$item) {
+      $item['access'] = $item['mlid'] == 1;
+    };
+
+    // Build the menu tree and check access for all of the items.
+    $tree = $this->menuTree->buildTreeData($items);
+    $this->menuTree->checkAccess($tree);
+
+    $this->assertCount(1, $tree);
+    $item = reset($tree);
+    $this->assertEquals($items[0], $item['link']);
+  }
+
+}
+
+class TestMenuTree extends MenuTree {
+
+  /**
+   * An alternative callable used for menuLinkTranslate.
+   * @var callable
+   */
+  public $menuLinkTranslateCallable;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function menuLinkTranslate(&$item) {
+    if (isset($this->menuLinkTranslateCallable)) {
+      call_user_func_array($this->menuLinkTranslateCallable, array(&$item));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function menuLinkGetPreferred($menu_name, $active_path) {
+  }
+
+}
diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module
index a97b447..93fa4b2 100644
--- a/core/modules/shortcut/shortcut.module
+++ b/core/modules/shortcut/shortcut.module
@@ -314,8 +314,6 @@ function shortcut_valid_link($path) {
  *
  * @return \Drupal\shortcut\ShortcutInterface[]
  *   An array of shortcut links, in the format returned by the menu system.
- *
- * @see menu_tree()
  */
 function shortcut_renderable_links($shortcut_set = NULL) {
   $shortcut_links = array();
diff --git a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMenuBlock.php b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMenuBlock.php
index 8298193..5e0d710 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMenuBlock.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemMenuBlock.php
@@ -8,7 +8,10 @@
 namespace Drupal\system\Plugin\Block;
 
 use Drupal\block\BlockBase;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\menu_link\MenuTreeInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Provides a generic Menu block.
@@ -20,14 +23,50 @@
  *   derivative = "Drupal\system\Plugin\Derivative\SystemMenuBlock"
  * )
  */
-class SystemMenuBlock extends BlockBase {
+class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The menu tree.
+   *
+   * @var \Drupal\menu_link\MenuTreeInterface
+   */
+  protected $menuTree;
+
+  /**
+   * Constructs a new SystemMenuBlock.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param array $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\menu_link\MenuTreeInterface $menu_tree
+   *   The menu tree.
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition, MenuTreeInterface $menu_tree) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->menuTree = $menu_tree;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('menu_link.tree')
+    );
+  }
 
   /**
    * {@inheritdoc}
    */
   public function build() {
     $menu = $this->getDerivativeId();
-    return menu_tree($menu);
+    return $this->menuTree->renderMenu($menu);
   }
 
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeAccessTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeAccessTest.php
deleted file mode 100644
index 974208f..0000000
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeAccessTest.php
+++ /dev/null
@@ -1,116 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\system\Tests\Menu\TreeAccessTest.
- */
-
-namespace Drupal\system\Tests\Menu;
-
-use Drupal\menu_link\Entity\MenuLink;
-use Drupal\simpletest\DrupalUnitTestBase;
-use Drupal\Core\DependencyInjection\ContainerBuilder;
-use Symfony\Component\Routing\Route;
-use Symfony\Component\Routing\RouteCollection;
-
-/**
- * Tests the access check for menu tree using both menu links and route items.
- */
-
-class TreeAccessTest extends DrupalUnitTestBase {
-
-  /**
-   * A list of menu links used for this test.
-   *
-   * @var array
-   */
-  protected $links;
-
-  /**
-   * The route collection used for this test.
-   *
-   * @var\ \Symfony\Component\Routing\RouteCollection
-   */
-  protected $routeCollection;
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = array('menu_link');
-
-  public static function getInfo() {
-    return array(
-      'name' => 'Menu tree access',
-      'description' => 'Tests the access check for menu tree using both menu links and route items.',
-      'group' => 'Menu',
-    );
-  }
-
-  /**
-   * Overrides \Drupal\simpletest\DrupalUnitTestBase::containerBuild().
-   */
-  public function containerBuild(ContainerBuilder $container) {
-    parent::containerBuild($container);
-
-    $route_collection = $this->getTestRouteCollection();
-
-    $container->register('router.route_provider', 'Drupal\system\Tests\Routing\MockRouteProvider')
-      ->addArgument($route_collection);
-  }
-
-  /**
-   * Generates the test route collection.
-   *
-   * @return \Symfony\Component\Routing\RouteCollection
-   *   Returns the test route collection.
-   */
-  protected function getTestRouteCollection() {
-    if (!isset($this->routeCollection)) {
-      $route_collection = new RouteCollection();
-      $route_collection->add('menu_test_1', new Route('/menu_test/test_1',
-        array(
-          '_controller' => '\Drupal\menu_test\TestController::test'
-        ),
-        array(
-          '_access' => 'TRUE'
-        )
-      ));
-      $route_collection->add('menu_test_2', new Route('/menu_test/test_2',
-        array(
-          '_controller' => '\Drupal\menu_test\TestController::test'
-        ),
-        array(
-          '_access' => 'FALSE'
-        )
-      ));
-      $this->routeCollection = $route_collection;
-    }
-
-    return $this->routeCollection;
-  }
-
-  /**
-   * Tests access check for menu links with a route item.
-   */
-  public function testRouteItemMenuLinksAccess() {
-    // Add the access checkers to the route items.
-    $this->container->get('access_manager')->setChecks($this->getTestRouteCollection());
-
-    // Setup the links with the route items.
-    $this->links = array(
-      new MenuLink(array('mlid' => 1, 'route_name' => 'menu_test_1', 'depth' => 1, 'link_path' => 'menu_test/test_1'), 'menu_link'),
-      new MenuLink(array('mlid' => 2, 'route_name' => 'menu_test_2', 'depth' => 1, 'link_path' => 'menu_test/test_2'), 'menu_link'),
-    );
-
-    // Build the menu tree and check access for all of the items.
-    $tree = menu_tree_data($this->links);
-    menu_tree_check_access($tree);
-
-    $this->assertEqual(count($tree), 1, 'Ensure that just one menu link got access.');
-    $item = reset($tree);
-    $this->assertEqual($this->links[0], $item['link'], 'Ensure that the right link got access');
-  }
-
-}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeDataUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeDataUnitTest.php
deleted file mode 100644
index 8b6c4a1..0000000
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeDataUnitTest.php
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\system\Tests\Menu\TreeDataUnitTest.
- */
-
-namespace Drupal\system\Tests\Menu;
-
-use Drupal\menu_link\Entity\MenuLink;
-use Drupal\simpletest\UnitTestBase;
-
-/**
- * Menu tree data related tests.
- */
-class TreeDataUnitTest extends UnitTestBase {
-  /**
-   * Dummy link structure acceptable for menu_tree_data().
-   */
-  protected $links = array();
-
-  public static function getInfo() {
-    return array(
-      'name' => 'Menu tree generation',
-      'description' => 'Tests recursive menu tree generation functions.',
-      'group' => 'Menu',
-    );
-  }
-
-  /**
-   * Validate the generation of a proper menu tree hierarchy.
-   */
-  public function testMenuTreeData() {
-    $this->links = array(
-      1 => new MenuLink(array('mlid' => 1, 'depth' => 1), 'menu_link'),
-      2 => new MenuLink(array('mlid' => 2, 'depth' => 1), 'menu_link'),
-      3 => new MenuLink(array('mlid' => 3, 'depth' => 2), 'menu_link'),
-      4 => new MenuLink(array('mlid' => 4, 'depth' => 3), 'menu_link'),
-      5 => new MenuLink(array('mlid' => 5, 'depth' => 1), 'menu_link'),
-    );
-
-    $tree = menu_tree_data($this->links);
-
-    // Validate that parent items #1, #2, and #5 exist on the root level.
-    $this->assertSameLink($this->links[1], $tree[1]['link'], 'Parent item #1 exists.');
-    $this->assertSameLink($this->links[2], $tree[2]['link'], 'Parent item #2 exists.');
-    $this->assertSameLink($this->links[5], $tree[5]['link'], 'Parent item #5 exists.');
-
-    // Validate that child item #4 exists at the correct location in the hierarchy.
-    $this->assertSameLink($this->links[4], $tree[2]['below'][3]['below'][4]['link'], 'Child item #4 exists in the hierarchy.');
-  }
-
-  /**
-   * Check that two menu links are the same by comparing the mlid.
-   *
-   * @param $link1
-   *   A menu link item.
-   * @param $link2
-   *   A menu link item.
-   * @param $message
-   *   The message to display along with the assertion.
-   * @return
-   *   TRUE if the assertion succeeded, FALSE otherwise.
-   */
-  protected function assertSameLink($link1, $link2, $message = '') {
-    return $this->assert($link1['mlid'] == $link2['mlid'], $message ?: 'First link is identical to second link');
-  }
-}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeOutputTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeOutputTest.php
deleted file mode 100644
index bc80cb5..0000000
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeOutputTest.php
+++ /dev/null
@@ -1,77 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\system\Tests\Menu\TreeOutputTest.
- */
-
-namespace Drupal\system\Tests\Menu;
-
-use Drupal\simpletest\DrupalUnitTestBase;
-
-/**
- * Menu tree output related tests.
- */
-class TreeOutputTest extends DrupalUnitTestBase {
-
-  public static $modules = array('system', 'menu_link', 'field');
-
-  /**
-   * Dummy link structure acceptable for menu_tree_output().
-   */
-  protected $tree_data = array();
-
-  public static function getInfo() {
-    return array(
-      'name' => 'Menu tree output',
-      'description' => 'Tests menu tree output functions.',
-      'group' => 'Menu',
-    );
-  }
-
-  function setUp() {
-    parent::setUp();
-
-    $this->installSchema('system', array('router'));
-  }
-
-  /**
-   * Validate the generation of a proper menu tree output.
-   */
-  function testMenuTreeData() {
-    $storage_controller = $this->container->get('entity.manager')->getStorageController('menu_link');
-    // @todo Prettify this tree buildup code, it's very hard to read.
-    $this->tree_data = array(
-      '1'=> array(
-        'link' => $storage_controller->create(array('menu_name' => 'main-menu', 'mlid' => 1, 'hidden' => 0, 'has_children' => 1, 'title' => 'Item 1', 'in_active_trail' => 1, 'access' => 1, 'link_path' => 'a', 'localized_options' => array('attributes' => array('title' =>'')))),
-        'below' => array(
-          '2' => array('link' => $storage_controller->create(array('menu_name' => 'main-menu', 'mlid' => 2, 'hidden' => 0, 'has_children' => 1, 'title' => 'Item 2', 'in_active_trail' => 1, 'access' => 1, 'link_path' => 'a/b', 'localized_options' => array('attributes' => array('title' =>'')))),
-            'below' => array(
-              '3' => array('link' => $storage_controller->create(array('menu_name' => 'main-menu', 'mlid' => 3, 'hidden' => 0, 'has_children' => 0, 'title' => 'Item 3', 'in_active_trail' => 0, 'access' => 1, 'link_path' => 'a/b/c', 'localized_options' => array('attributes' => array('title' =>'')))),
-                'below' => array() ),
-              '4' => array('link' => $storage_controller->create(array('menu_name' => 'main-menu', 'mlid' => 4, 'hidden' => 0, 'has_children' => 0, 'title' => 'Item 4', 'in_active_trail' => 0, 'access' => 1, 'link_path' => 'a/b/d', 'localized_options' => array('attributes' => array('title' =>'')))),
-                'below' => array() )
-              )
-            )
-          )
-        ),
-      '5' => array('link' => $storage_controller->create(array('menu_name' => 'main-menu', 'mlid' => 5, 'hidden' => 1, 'has_children' => 0, 'title' => 'Item 5', 'in_active_trail' => 0, 'access' => 1, 'link_path' => 'e', 'localized_options' => array('attributes' => array('title' =>'')))), 'below' => array()),
-      '6' => array('link' => $storage_controller->create(array('menu_name' => 'main-menu', 'mlid' => 6, 'hidden' => 0, 'has_children' => 0, 'title' => 'Item 6', 'in_active_trail' => 0, 'access' => 0, 'link_path' => 'f', 'localized_options' => array('attributes' => array('title' =>'')))), 'below' => array()),
-      '7' => array('link' => $storage_controller->create(array('menu_name' => 'main-menu', 'mlid' => 7, 'hidden' => 0, 'has_children' => 0, 'title' => 'Item 7', 'in_active_trail' => 0, 'access' => 1, 'link_path' => 'g', 'localized_options' => array('attributes' => array('title' =>'')))), 'below' => array())
-    );
-
-    $output = menu_tree_output($this->tree_data);
-
-    // Validate that the - in main-menu is changed into an underscore
-    $this->assertEqual($output['1']['#theme'], 'menu_link__main_menu', 'Hyphen is changed to an underscore on menu_link');
-    $this->assertEqual($output['#theme_wrappers'][0], 'menu_tree__main_menu', 'Hyphen is changed to an underscore on menu_tree wrapper');
-    // Looking for child items in the data
-    $this->assertEqual( $output['1']['#below']['2']['#href'], 'a/b', 'Checking the href on a child item');
-    $this->assertTrue( in_array('active-trail',$output['1']['#below']['2']['#attributes']['class']) , 'Checking the active trail class');
-    // Validate that the hidden and no access items are missing
-    $this->assertFalse( isset($output['5']), 'Hidden item should be missing');
-    $this->assertFalse( isset($output['6']), 'False access should be missing');
-    // Item 7 is after a couple hidden items. Just to make sure that 5 and 6 are skipped and 7 still included
-    $this->assertTrue( isset($output['7']), 'Item after hidden items is present');
-  }
-}
diff --git a/core/modules/system/tests/modules/menu_test/menu_test.module b/core/modules/system/tests/modules/menu_test/menu_test.module
index 08e6382..1c8a295 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.module
+++ b/core/modules/system/tests/modules/menu_test/menu_test.module
@@ -219,8 +219,10 @@ function menu_test_callback() {
  */
 function menu_test_menu_trail_callback() {
   $menu_path = \Drupal::state()->get('menu_test.menu_tree_set_path') ?: array();
+  /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
+  $menu_tree = \Drupal::service('menu_link.tree');
   if (!empty($menu_path)) {
-    menu_tree_set_path($menu_path['menu_name'], $menu_path['path']);
+    $menu_tree->setPath($menu_path['menu_name'], $menu_path['path']);
   }
   return 'This is menu_test_menu_trail_callback().';
 }
diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module
index 7217986..dad1cb1 100644
--- a/core/modules/toolbar/toolbar.module
+++ b/core/modules/toolbar/toolbar.module
@@ -358,6 +358,9 @@ function toolbar_toolbar() {
   // Add attributes to the links before rendering.
   toolbar_menu_navigation_links($tree);
 
+  /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
+  $menu_tree = \Drupal::service('menu_link.tree');
+
   $menu = array(
     '#heading' => t('Administration menu'),
     'toolbar_administration' => array(
@@ -365,7 +368,7 @@ function toolbar_toolbar() {
       '#attributes' => array(
         'class' => array('toolbar-menu-administration'),
       ),
-      'administration_menu' => menu_tree_output($tree),
+      'administration_menu' => $menu_tree->renderTree($tree),
     ),
   );
 
@@ -417,6 +420,8 @@ function toolbar_toolbar() {
  */
 function toolbar_get_menu_tree() {
   $tree = array();
+  /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
+  $menu_tree = \Drupal::service('menu_link.tree');
   $query = \Drupal::entityQuery('menu_link')
     ->condition('menu_name', 'admin')
     ->condition('module', 'system')
@@ -424,7 +429,7 @@ function toolbar_get_menu_tree() {
   $result = $query->execute();
   if (!empty($result)) {
     $admin_link = menu_link_load(reset($result));
-    $tree = menu_build_tree('admin', array(
+    $tree = $menu_tree->buildTree('admin', array(
       'expanded' => array($admin_link['mlid']),
       'min_depth' => $admin_link['depth'] + 1,
       'max_depth' => $admin_link['depth'] + 1,
@@ -467,6 +472,8 @@ function toolbar_menu_navigation_links(&$tree) {
  */
 function toolbar_get_rendered_subtrees() {
   $subtrees = array();
+  /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
+  $menu_tree = \Drupal::service('menu_link.tree');
   $tree = toolbar_get_menu_tree();
   foreach ($tree as $tree_item) {
     $item = $tree_item['link'];
@@ -479,9 +486,9 @@ function toolbar_get_rendered_subtrees() {
           $query->condition('p' . $i, $item['p' . $i]);
         }
         $parents = $query->execute()->fetchCol();
-        $subtree = menu_build_tree($item['menu_name'], array('expanded' => $parents, 'min_depth' => $item['depth']+1));
+        $subtree = $menu_tree->buildTree($item['menu_name'], array('expanded' => $parents, 'min_depth' => $item['depth']+1));
         toolbar_menu_navigation_links($subtree);
-        $subtree = menu_tree_output($subtree);
+        $subtree = $menu_tree->renderTree($subtree);
         $subtree = drupal_render($subtree);
       }
       else {
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserAccountLinksTests.php b/core/modules/user/lib/Drupal/user/Tests/UserAccountLinksTests.php
index bbe5a71..c9dcd30 100644
--- a/core/modules/user/lib/Drupal/user/Tests/UserAccountLinksTests.php
+++ b/core/modules/user/lib/Drupal/user/Tests/UserAccountLinksTests.php
@@ -67,7 +67,9 @@ function testSecondaryMenu() {
     $this->drupalGet('<front>');
 
     // For a logged-out user, expect no secondary links.
-    $tree = menu_build_tree('account');
+    /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
+    $menu_tree = \Drupal::service('menu_link.tree');
+    $tree = $menu_tree->buildTree('account');
     $this->assertEqual(count($tree), 1, 'The secondary links menu contains only one menu link.');
     $link = reset($tree);
     $link = $link['link'];
