diff --git a/core/core.services.yml b/core/core.services.yml
index 562d64f..fd9028c 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -277,7 +277,7 @@ services:
     arguments: ['@menu.tree_storage', '@menu_link.static.overrides', '@module_handler']
   menu.link_tree:
     class: Drupal\Core\Menu\MenuLinkTree
-    arguments: ['@menu.tree_storage', '@plugin.manager.menu.link', '@router.route_provider', '@menu.active_trail', '@controller_resolver']
+    arguments: ['@menu.tree_storage', '@plugin.manager.menu.link', '@router.route_provider', '@menu.active_trail', '@controller_resolver', '@cache.menu', '@current_route_match']
   menu.default_tree_manipulators:
     class: Drupal\Core\Menu\DefaultMenuLinkTreeManipulators
     arguments: ['@access_manager', '@current_user']
@@ -443,7 +443,12 @@ services:
     arguments: ['@router.dumper', '@lock', '@event_dispatcher', '@module_handler', '@controller_resolver', '@state']
   router.rebuild_subscriber:
     class: Drupal\Core\EventSubscriber\RouterRebuildSubscriber
-    arguments: ['@router.builder', '@lock']
+    arguments: ['@router.builder']
+    tags:
+      - { name: event_subscriber }
+  menu.rebuild_subscriber:
+    class: Drupal\Core\EventSubscriber\MenuRouterRebuildSubscriber
+    arguments: ['@lock', '@plugin.manager.menu.link']
     tags:
       - { name: event_subscriber }
   path.alias_storage:
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 8f1ce73..bd38752 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -153,8 +153,8 @@
  * - weight: Lower (negative) numbers come before higher (positive) numbers,
  *   for menu items with the same parent.
  *
- * Menu items from other modules can be altered using
- * hook_menu_link_defaults_alter().
+ * Discovered menu links from other modules can be altered using
+ * hook_menu_links_discovered_alter().
  *
  * @todo Derivatives will probably be defined for these; when they are, add
  *   documentation here.
@@ -256,135 +256,98 @@
  */
 
 /**
- * The maximum depth of a menu links tree - matches the number of p columns.
+ * @section Rendering menus
+ * Once you have created menus (that contain menu links), you want to render
+ * them. Drupal provides a block (Drupal\system\Plugin\Block\SystemMenuBlock) to
+ * do so.
+ *
+ * However, perhaps you have more advanced needs and you're not satisfied with
+ * what the menu blocks offer you. If that's the case, you'll want to:
+ * - Instantiate \Drupal\Core\Menu\MenuTreeParameters, and set its values to
+ *   match your needs. Alternatively, you can use
+ *   MenuLinkTree::getCurrentRouteMenuTreeParameters() to get a typical
+ *   default set of parameters, and then customize them to suit your needs.
+ * - Call \Drupal\Core\MenuLinkTree::load() with your menu link tree parameters,
+ *   this will return a menu link tree.
+ * - Pass the menu tree to \Drupal\Core\Menu\MenuLinkTree::transform() to apply
+ *   menu link tree manipulators that transform the tree. You will almost always
+ *   want to apply access checking. The manipulators that you will typically
+ *   need can be found in \Drupal\Core\Menu\DefaultMenuTreeManipulators.
+ * - Potentially write a custom menu tree manipulator, see
+ *   \Drupal\Core\Menu\DefaultMenuTreeManipulators for examples. This is only
+ *   necessary if you want to do things like adding extra metadata to rendered
+ *   links to display icons next to them.
+ * - Pass the menu tree to \Drupal\Core\Menu\MenuLinkTree::build(), this will
+ *   build a renderable array.
+ *
+ * Combined, that would look like this:
+ * @code
+ * $menu_tree = \Drupal::menuTree();
+ * $menu_name = 'my_menu';
  *
- * @todo Move this constant to MenuLinkStorage along with all the tree
- * functionality.
- */
-const MENU_MAX_DEPTH = 9;
-
-/**
- * Reserved key to identify the most specific menu link for a given path.
+ * // Build the typical default set of menu tree parameters.
+ * $parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name);
  *
- * The value of this constant is a hash of the constant name. We use the hash
- * so that the reserved key is over 32 characters in length and will not
- * collide with allowed menu names:
- * @code
- * sha1('MENU_PREFERRED_LINK') = 1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91
- * @endcode
+ * // Load the tree based on this set of parameters.
+ * $tree = $menu_tree->load($menu_name, $parameters);
  *
- * @see menu_link_get_preferred()
- */
-const MENU_PREFERRED_LINK = '1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91';
-
-/**
- * Localizes a menu link title using t() if possible.
+ * // Transform the tree using the manipulators you want.
+ * $manipulators = array(
+ *   // Only show links that are accessible for the current user.
+ *   array('callable' => 'menu.default_tree_manipulators:checkAccess'),
+ *   // Use the default sorting of menu links.
+ *   array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
+ * );
+ * $tree = $menu_tree->transform($tree, $manipulators);
  *
- * Translate the title and description to allow storage of English title
- * strings in the database, yet display of them in the language required
- * by the current user.
+ * // Finally, build a renderable array from the transformed tree.
+ * $menu = $menu_tree->build($tree);
  *
- * @param $item
- *   A menu link entity.
+ * $menu_html = drupal_render($menu);
+ * @endcode
  */
-function _menu_item_localize(&$item) {
-  // Allow default menu links to be translated.
-  $item['localized_options'] = $item['options'];
-  // All 'class' attributes are assumed to be an array during rendering, but
-  // links stored in the database may use an old string value.
-  // @todo In order to remove this code we need to implement a database update
-  //   including unserializing all existing link options and running this code
-  //   on them, as well as adding validation to menu_link_save().
-  if (isset($item['options']['attributes']['class']) && is_string($item['options']['attributes']['class'])) {
-    $item['localized_options']['attributes']['class'] = explode(' ', $item['options']['attributes']['class']);
-  }
-  // If the menu link is defined in code and not customized, we can use t().
-  if (!empty($item['machine_name']) && !$item['customized']) {
-    // @todo Figure out a proper way to support translations of menu links, see
-    //   https://drupal.org/node/2193777.
-    $item['title'] = t($item['link_title']);
-  }
-  else {
-    $item['title'] = $item['link_title'];
-  }
-}
 
 /**
- * Provides menu link unserializing, access control, and argument handling.
- *
- * @param array $item
- *   The passed in item has the following keys:
- *   - access: (optional) Becomes TRUE if the item is accessible, FALSE
- *     otherwise. If the key is not set, the access manager is used to
- *     determine the access.
- *   - options: (required) Is unserialized and copied to $item['localized_options'].
- *   - link_title: (required) The title of the menu link.
- *   - route_name: (required) The route name of the menu link.
- *   - route_parameters: (required) The unserialized route parameters of the menu link.
- *   The passed in item is changed by the following keys:
- *   - href: The actual path to the link. This path is generated from the
- *     link_path of the menu link entity.
- *   - title: The title of the link. This title is generated from the
- *     link_title of the menu link entity.
+ * Implements template_preprocess_HOOK() for theme_menu_tree().
  */
-function _menu_link_translate(&$item) {
-  if (!is_array($item['options'])) {
-    $item['options'] = (array) unserialize($item['options']);
-  }
-  $item['localized_options'] = $item['options'];
-  $item['title'] = $item['link_title'];
-  if ($item['external'] || empty($item['route_name'])) {
-    $item['access'] = 1;
-    $item['href'] = $item['link_path'];
-    $item['route_parameters'] = array();
-    // Set to NULL so that drupal_pre_render_link() is certain to skip it.
-    $item['route_name'] = NULL;
-  }
-  else {
-    $item['href'] = NULL;
-    if (!is_array($item['route_parameters'])) {
-      $item['route_parameters'] = (array) unserialize($item['route_parameters']);
-    }
-    // menu_tree_check_access() may set this ahead of time for links to nodes.
-    if (!isset($item['access'])) {
-      $item['access'] = \Drupal::getContainer()->get('access_manager')->checkNamedRoute($item['route_name'], $item['route_parameters'], \Drupal::currentUser());
+function template_preprocess_menu_tree(&$variables) {
+  if (isset($variables['tree']['#heading'])) {
+    $variables['heading'] = $variables['tree']['#heading'];
+    $heading = &$variables['heading'];
+    // Convert a string heading into an array, using a H2 tag by default.
+    if (is_string($heading)) {
+      $heading = array('text' => $heading);
     }
-    // For performance, don't localize a link the user can't access.
-    if ($item['access']) {
-      _menu_item_localize($item);
+    // Merge in default array properties into $heading.
+    $heading += array(
+      'level' => 'h2',
+      'attributes' => array(),
+    );
+    // @todo Remove backwards compatibility for $heading['class'].
+    //   https://www.drupal.org/node/2310341
+    if (isset($heading['class'])) {
+      $heading['attributes']['class'] = $heading['class'];
     }
+    // Convert the attributes array into an Attribute object.
+    $heading['attributes'] = new Attribute($heading['attributes']);
+    $heading['text'] = String::checkPlain($heading['text']);
   }
 
-  // Allow other customizations - e.g. adding a page-specific query string to the
-  // options array. For performance reasons we only invoke this hook if the link
-  // has the 'alter' flag set in the options array.
-  if (!empty($item['options']['alter'])) {
-    \Drupal::moduleHandler()->alter('translated_menu_link', $item, $map);
+  if (isset($variables['tree']['#attributes'])) {
+    $variables['attributes'] = new Attribute($variables['tree']['#attributes']);
   }
-}
+  else {
+    $variables['attributes'] = new Attribute();
+  }
+  if (!isset($variables['attributes']['class'])) {
+    $variables['attributes']['class'] = array();
+  }
+  $variables['attributes']['class'][] = 'menu';
 
-/**
- * Implements template_preprocess_HOOK() for theme_menu_tree().
- */
-function template_preprocess_menu_tree(&$variables) {
   $variables['tree'] = $variables['tree']['#children'];
 }
 
 /**
- * Returns HTML for a wrapper for a menu sub-tree.
- *
- * @param $variables
- *   An associative array containing:
- *   - tree: An HTML string containing the tree's items.
- *
- * @see template_preprocess_menu_tree()
- * @ingroup themeable
- */
-function theme_menu_tree($variables) {
-  return '<ul class="menu">' . $variables['tree'] . '</ul>';
-}
-
-/**
  * Returns HTML for a menu link and submenu.
  *
  * @param $variables
@@ -400,8 +363,10 @@ function theme_menu_link(array $variables) {
   if ($element['#below']) {
     $sub_menu = drupal_render($element['#below']);
   }
-  $element['#localized_options']['set_active_class'] = TRUE;
-  $output = l($element['#title'], $element['#href'], $element['#localized_options']);
+  /** @var \Drupal\Core\Url $url */
+  $url = $element['#url'];
+  $url->setOption('set_active_class', TRUE);
+  $output = \Drupal::linkGenerator()->generateFromUrl($element['#title'], $url);
   return '<li' . new Attribute($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
 }
 
@@ -541,56 +506,28 @@ function _menu_get_links_source($name, $default) {
 }
 
 /**
- * Returns an array of links for a navigation menu.
+ * Builds a renderable array for a navigation menu.
  *
- * @param $menu_name
+ * @param string $menu_name
  *   The name of the menu.
- * @param $level
+ * @param int $level
  *   Optional, the depth of the menu to be returned.
  *
- * @return
- *   An array of links of the specified menu and level.
+ * @return array
+ *   A renderable array.
  */
 function menu_navigation_links($menu_name, $level = 0) {
-  // Don't even bother querying the menu table if no menu is specified.
-  if (empty($menu_name)) {
-    return array();
-  }
-
-  // Get the menu hierarchy for the current page.
-  /** @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) {
-    // Loop through the current level's items until we find one that is in trail.
-    while ($item = array_shift($tree)) {
-      if ($item['link']['in_active_trail']) {
-        // If the item is in the active trail, we continue in the subtree.
-        $tree = empty($item['below']) ? array() : $item['below'];
-        break;
-      }
-    }
-  }
-
-  // Create a single level of links.
-  $links = array();
-  foreach ($tree as $item) {
-    if (!$item['link']['hidden']) {
-      $class = '';
-      $l = $item['link']['localized_options'];
-      $l['href'] = $item['link']['link_path'];
-      $l['title'] = $item['link']['title'];
-      if ($item['link']['in_active_trail']) {
-        $class = ' active-trail';
-        $l['attributes']['class'][] = 'active-trail';
-      }
-      // Keyed with the unique mlid to generate classes in links.html.twig.
-      $links['menu-' . $item['link']['mlid'] . $class] = $l;
-    }
-  }
-  return $links;
+  $menu_tree = \Drupal::menuTree();
+  $parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name);
+  $parameters->setMaxDepth($level + 1);
+  $tree = $menu_tree->load($menu_name, $parameters);
+  $manipulators = array(
+    array('callable' => 'menu.default_tree_manipulators:checkAccess'),
+    array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
+    array('callable' => 'menu.default_tree_manipulators:extractSubtreeOfActiveTrail', 'args' => array($level)),
+  );
+  $tree = $menu_tree->transform($tree, $manipulators);
+  return $menu_tree->build($tree);
 }
 
 /**
@@ -734,355 +671,13 @@ function theme_menu_local_tasks(&$variables) {
 }
 
 /**
- * Sets (or gets) the active menu for the current page.
- *
- * The active menu for the page determines the active trail.
- *
- * @return
- *   An array of menu machine names, in order of preference. The
- *   'system.menu:active_menus_default' config item may be used to assert a menu
- *   order different from the order of creation, or to prevent a particular menu
- *   from being used at all in the active trail.
- */
-function menu_set_active_menu_names($menu_names = NULL) {
-  $active = &drupal_static(__FUNCTION__);
-
-  if (isset($menu_names) && is_array($menu_names)) {
-    $active = $menu_names;
-  }
-  elseif (!isset($active)) {
-    $config = \Drupal::config('system.menu');
-    $active = $config->get('active_menus_default') ?: array_keys(menu_list_system_menus());
-  }
-  return $active;
-}
-
-/**
- * Gets the active menu for the current page.
- */
-function menu_get_active_menu_names() {
-  return menu_set_active_menu_names();
-}
-
-/**
- * Looks up the preferred menu link for a given system path.
- *
- * @param $path
- *   The path; for example, 'node/5'. The function will find the corresponding
- *   menu link ('node/5' if it exists, or fallback to 'node/%').
- * @param $selected_menu
- *   The name of a menu used to restrict the search for a preferred menu link.
- *   If not specified, all the menus returned by menu_get_active_menu_names()
- *   will be used.
- *
- * @return
- *   A fully translated menu link, or FALSE if no matching menu link was
- *   found. The most specific menu link ('node/5' preferred over 'node/%') in
- *   the most preferred menu (as defined by menu_get_active_menu_names()) is
- *   returned.
- */
-function menu_link_get_preferred($path = NULL, $selected_menu = NULL) {
-  $preferred_links = &drupal_static(__FUNCTION__);
-
-  if (!isset($path)) {
-    $path = current_path();
-  }
-
-  if (empty($selected_menu)) {
-    // Use an illegal menu name as the key for the preferred menu link.
-    $selected_menu = MENU_PREFERRED_LINK;
-  }
-
-  if (!isset($preferred_links[$path])) {
-    // Look for the correct menu link by building a list of candidate paths,
-    // which are ordered by priority (translated hrefs are preferred over
-    // untranslated paths). Afterwards, the most relevant path is picked from
-    // the menus, ordered by menu preference.
-    $path_candidates = array();
-    // 1. The current item href.
-    // @todo simplify this code and convert to using route names.
-    // @see https://drupal.org/node/2154949
-    $path_candidates[$path] = $path;
-
-    // Retrieve a list of menu names, ordered by preference.
-    $menu_names = menu_get_active_menu_names();
-    // Put the selected menu at the front of the list.
-    array_unshift($menu_names, $selected_menu);
-
-    $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => $path_candidates));
-
-    // Sort candidates by link path and menu name.
-    $candidates = array();
-    foreach ($menu_links as $candidate) {
-      $candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate;
-      // Add any menus not already in the menu name search list.
-      if (!in_array($candidate['menu_name'], $menu_names)) {
-        $menu_names[] = $candidate['menu_name'];
-      }
-    }
-
-    // Store the most specific link for each menu. Also save the most specific
-    // link of the most preferred menu in $preferred_link.
-    foreach ($path_candidates as $link_path) {
-      if (isset($candidates[$link_path])) {
-        foreach ($menu_names as $menu_name) {
-          if (empty($preferred_links[$path][$menu_name]) && isset($candidates[$link_path][$menu_name])) {
-            $candidate_item = $candidates[$link_path][$menu_name];
-            $candidate_item['access'] = \Drupal::service('access_manager')->checkNamedRoute($candidate_item['route_name'], $candidate_item['route_parameters'], \Drupal::currentUser());
-            if ($candidate_item['access']) {
-              _menu_item_localize($candidate_item);
-              $preferred_links[$path][$menu_name] = $candidate_item;
-              if (empty($preferred_links[$path][MENU_PREFERRED_LINK])) {
-                // Store the most specific link.
-                $preferred_links[$path][MENU_PREFERRED_LINK] = $candidate_item;
-              }
-            }
-          }
-        }
-      }
-    }
-  }
-
-  return isset($preferred_links[$path][$selected_menu]) ? $preferred_links[$path][$selected_menu] : FALSE;
-}
-
-/**
  * Clears all cached menu data.
  *
  * This should be called any time broad changes
  * might have been made to the router items or menu links.
  */
 function menu_cache_clear_all() {
-  \Drupal::cache('data')->deleteAll();
-  menu_reset_static_cache();
-}
-
-/**
- * Resets the menu system static cache.
- */
-function menu_reset_static_cache() {
-  \Drupal::entityManager()
-    ->getStorage('menu_link')->resetCache();
-  drupal_static_reset('menu_link_get_preferred');
-}
-
-/**
- * Saves menu links recursively for menu_links_rebuild_defaults().
- */
-function _menu_link_save_recursive($controller, $machine_name, &$children, &$links) {
-  $menu_link = $links[$machine_name];
-  if ($menu_link->isNew() || !$menu_link->customized) {
-    if (!isset($menu_link->plid) && !empty($menu_link->parent) && !empty($links[$menu_link->parent])) {
-      $parent = $links[$menu_link->parent];
-
-      if (empty($menu_link->menu_name) || $parent->menu_name == $menu_link->menu_name) {
-        $menu_link->plid = $parent->id();
-        $menu_link->menu_name = $parent->menu_name;
-      }
-    }
-    $controller->save($menu_link);
-  }
-  if (!empty($children[$machine_name])) {
-    foreach ($children[$machine_name] as $next_name) {
-      _menu_link_save_recursive($controller, $next_name, $children, $links);
-    }
-  }
-  // Remove processed link names so we can find stragglers.
-  unset($children[$machine_name]);
-}
-
-/**
- * Builds menu links for the items returned from the menu_link.static service.
- */
-function menu_link_rebuild_defaults() {
-  // Ensure that all configuration used to build the menu items are loaded
-  // without overrides.
-  $old_state = \Drupal::configFactory()->getOverrideState();
-  \Drupal::configFactory()->setOverrideState(FALSE);
-  $module_handler = \Drupal::moduleHandler();
-  if (!$module_handler->moduleExists('menu_link')) {
-    // The Menu link module may not be available during install, so rebuild
-    // when possible.
-    return;
-  }
-  /** @var \Drupal\menu_link\MenuLinkStorageInterface $menu_link_storage */
-  $menu_link_storage = \Drupal::entityManager()
-    ->getStorage('menu_link');
-  $links = array();
-  $children = array();
-  $top_links = array();
-  $all_links = \Drupal::service('menu_link.static')->getLinks();
-  if ($all_links) {
-    foreach ($all_links as $machine_name => $link) {
-      // For performance reasons, do a straight query now and convert to a menu
-      // link entity later.
-      // @todo revisit before release.
-      $existing_item = db_select('menu_links')
-        ->fields('menu_links')
-        ->condition('machine_name', $machine_name)
-        ->execute()->fetchObject();
-      if ($existing_item) {
-        $existing_item->options = unserialize($existing_item->options);
-        $existing_item->route_parameters = unserialize($existing_item->route_parameters);
-        $link['mlid'] = $existing_item->mlid;
-        $link['plid'] = $existing_item->plid;
-        $link['uuid'] = $existing_item->uuid;
-        $link['customized'] = $existing_item->customized;
-        $link['updated'] = $existing_item->updated;
-        $menu_link = $menu_link_storage->createFromDefaultLink($link);
-        // @todo Do not create a new entity in order to update it, see
-        //   https://drupal.org/node/2241865
-        $menu_link->setOriginalId($existing_item->mlid);
-
-        // Convert the existing item to a typed object.
-        /** @var \Drupal\menu_link\MenuLinkInterface $existing_item */
-        $existing_item = $menu_link_storage->create(get_object_vars($existing_item));
-
-        if (!$existing_item->customized) {
-          // A change in the default menu links may move the link to a
-          // different menu or parent.
-          if (!empty($link['menu_name']) && ($link['menu_name'] != $existing_item->menu_name)) {
-            $menu_link->plid = NULL;
-            $menu_link->menu_name = $link['menu_name'];
-          }
-          elseif (!empty($link['parent'])) {
-            $menu_link->plid = NULL;
-          }
-
-          $menu_link->original = $existing_item;
-        }
-      }
-      else {
-        if (empty($link['route_name']) && empty($link['link_path'])) {
-          \Drupal::logger('menu_link')->error('Menu_link %machine_name does neither provide a route_name nor a link_path, so it got skipped.', array('%machine_name' => $machine_name));
-          continue;
-        }
-        $menu_link = $menu_link_storage->createFromDefaultLink($link);
-      }
-      if (!empty($link['parent'])) {
-        $children[$link['parent']][$machine_name] = $machine_name;
-        $menu_link->parent = $link['parent'];
-        if (empty($link['menu_name'])) {
-          // Reset the default menu name so it is populated from the parent.
-          $menu_link->menu_name = NULL;
-        }
-      }
-      else {
-        // A top level link - we need them to root our tree.
-        $top_links[$machine_name] = $machine_name;
-        $menu_link->plid = 0;
-      }
-      $links[$machine_name] = $menu_link;
-    }
-  }
-  foreach ($top_links as $machine_name) {
-    _menu_link_save_recursive($menu_link_storage, $machine_name, $children, $links);
-  }
-  // Handle any children we didn't find starting from top-level links.
-  foreach ($children as $orphan_links) {
-    foreach ($orphan_links as $machine_name) {
-      // Force it to the top level.
-      $links[$machine_name]->plid = 0;
-      _menu_link_save_recursive($menu_link_storage, $machine_name, $children, $links);
-    }
-  }
-
-  // Find any item whose default menu link no longer exists.
-  if ($all_links) {
-    $query = \Drupal::entityQuery('menu_link')
-      ->condition('machine_name', array_keys($all_links), 'NOT IN')
-      ->exists('machine_name')
-      ->condition('external', 0)
-      ->condition('updated', 0)
-      ->condition('customized', 0)
-      ->sort('depth', 'DESC');
-    $result = $query->execute();
-  }
-  else {
-    $result = array();
-  }
-
-  // Remove all such items. Starting from those with the greatest depth will
-  // minimize the amount of re-parenting done by the menu link controller.
-  if ($result) {
-    menu_link_delete_multiple($result, TRUE);
-  }
-  \Drupal::configFactory()->setOverrideState($old_state);
-}
-
-/**
- * Returns an array containing all links for a menu.
- *
- * @param $menu_name
- *   The name of the menu whose links should be returned.
- *
- * @return
- *   An array of menu links.
- */
-function menu_load_links($menu_name) {
-  $links = array();
-
-  $query = \Drupal::entityQuery('menu_link')
-    ->condition('menu_name', $menu_name)
-    // Order by weight so as to be helpful for menus that are only one level
-    // deep.
-    ->sort('weight');
-  $result = $query->execute();
-
-  if (!empty($result)) {
-    $links = menu_link_load_multiple($result);
-  }
-
-  return $links;
-}
-
-/**
- * Deletes all links for a menu.
- *
- * @param $menu_name
- *   The name of the menu whose links will be deleted.
- */
-function menu_delete_links($menu_name) {
-  $links = menu_load_links($menu_name);
-  menu_link_delete_multiple(array_keys($links), FALSE, TRUE);
-}
-
-/**
- * Updates the expanded menu item state at most twice per page load.
- */
-function _menu_update_expanded_menus() {
-  $expanded_menus_updated = &drupal_static(__FUNCTION__, 0);
-
-  // Update the expanded menu item state, but at most twice, including at
-  //  the end of the page load when there are multiple links saved or deleted.
-  if ($expanded_menus_updated == 0) {
-    // Keep track of which menus have expanded items.
-    _menu_set_expanded_menus();
-    $expanded_menus_updated = 1;
-  }
-  elseif ($expanded_menus_updated == 1) {
-    // Keep track of which menus have expanded items.
-    drupal_register_shutdown_function('_menu_set_expanded_menus');
-    $expanded_menus_updated = 2;
-  }
-}
-
-/**
- * Updates a list of menus with expanded items.
- */
-function _menu_set_expanded_menus() {
-  $names = array();
-  $result = Drupal::entityQueryAggregate('menu_link')
-    ->condition('expanded', 0, '<>')
-    ->groupBy('menu_name')
-    ->execute();
-
-  // Flatten the resulting array.
-  foreach($result as $k => $v) {
-    $names[$k] = $v['menu_name'];
-  }
-
-  \Drupal::state()->set('menu_expanded', $names);
+  \Drupal::cache('menu')->invalidateAll();
 }
 
 /**
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 248c795..1a20acb 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -2126,27 +2126,17 @@ function template_preprocess_page(&$variables) {
 
   // Pass the main menu and secondary menu to the template as render arrays.
   if (!empty($variables['main_menu'])) {
-    $variables['main_menu'] = array(
-      '#theme' =>'links__system_main_menu',
-      '#links' => $variables['main_menu'],
-      '#heading' => array(
-        'text' => t('Main menu'),
-        'class' => array('visually-hidden'),
-        'attributes' => array('id' => 'links__system_main_menu'),
-      ),
-      '#set_active_class' => TRUE,
+    $variables['main_menu']['#heading'] = array(
+      'text' => t('Main menu'),
+      'class' => array('visually-hidden'),
+      'attributes' => array('id' => 'links__system_main_menu'),
     );
   }
   if (!empty($variables['secondary_menu'])) {
-    $variables['secondary_menu'] = array(
-      '#theme' =>'links__system_secondary_menu',
-      '#links' => $variables['secondary_menu'],
-      '#heading' => array(
-        'text' => t('Secondary menu'),
-        'class' => array('visually-hidden'),
-        'attributes' => array('id' => 'links__system_secondary_menu'),
-      ),
-      '#set_active_class' => TRUE,
+    $variables['secondary_menu']['#heading'] = array(
+      'text' => t('Secondary menu'),
+      'class' => array('visually-hidden'),
+      'attributes' => array('id' => 'links__system_secondary_menu'),
     );
   }
 
@@ -2632,6 +2622,7 @@ function drupal_common_theme() {
     ),
     'menu_tree' => array(
       'render element' => 'tree',
+      'template' => 'menu-tree',
     ),
     'menu_local_task' => array(
       'render element' => 'element',
diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php
index 21443e5..e20af77 100644
--- a/core/lib/Drupal.php
+++ b/core/lib/Drupal.php
@@ -647,4 +647,14 @@ public static function logger($channel) {
     return static::$container->get('logger.factory')->get($channel);
   }
 
+  /**
+   * Returns the menu tree.
+   *
+   * @return \Drupal\Core\Menu\MenuLinkTreeInterface
+   *   The menu tree.
+   */
+  public static function menuTree() {
+    return static::$container->get('menu.link_tree');
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
index facca39..23ce020 100644
--- a/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
+++ b/core/lib/Drupal/Core/Entity/Query/Sql/Tables.php
@@ -255,10 +255,7 @@ protected function getTableMapping($table, $entity_type_id) {
       $mapping = $storage->getTableMapping()->getAllColumns($table);
     }
     else {
-      // @todo Stop calling drupal_get_schema() once menu links are converted
-      //   to the Entity Field API. See https://drupal.org/node/1842858.
-      $schema = drupal_get_schema($table);
-      $mapping = array_keys($schema['fields']);
+      return FALSE;
     }
     return array_flip($mapping);
   }
diff --git a/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MenuRouterRebuildSubscriber.php
similarity index 59%
copy from core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php
copy to core/lib/Drupal/Core/EventSubscriber/MenuRouterRebuildSubscriber.php
index 87fdb87..4b0b41f 100644
--- a/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/MenuRouterRebuildSubscriber.php
@@ -2,24 +2,22 @@
 
 /**
  * @file
- * Contains \Drupal\Core\EventSubscriber\RouterRebuildSubscriber.
+ * Contains \Drupal\Core\EventSubscriber\MenuRouterRebuildSubscriber.
  */
 
 namespace Drupal\Core\EventSubscriber;
 
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Lock\LockBackendInterface;
-use Drupal\Core\Routing\RouteBuilderInterface;
+use Drupal\Core\Menu\MenuLinkManagerInterface;
 use Drupal\Core\Routing\RoutingEvents;
 use Symfony\Component\EventDispatcher\Event;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-use Symfony\Component\HttpKernel\Event\PostResponseEvent;
-use Symfony\Component\HttpKernel\KernelEvents;
 
 /**
  * Rebuilds the default menu links and runs menu-specific code if necessary.
  */
-class RouterRebuildSubscriber implements EventSubscriberInterface {
+class MenuRouterRebuildSubscriber implements EventSubscriberInterface {
 
   /**
    * @var \Drupal\Core\Routing\RouteBuilderInterface
@@ -32,26 +30,23 @@ class RouterRebuildSubscriber implements EventSubscriberInterface {
   protected $lock;
 
   /**
-   * Constructs the RouterRebuildSubscriber object.
+   * The menu link plugin manager.
    *
-   * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
-   *   The route builder.
-   * @param \Drupal\Core\Lock\LockBackendInterface $lock
-   *   The lock backend.
+   * @var \Drupal\Core\Menu\MenuLinkManagerInterface $menuLinkManager
    */
-  public function __construct(RouteBuilderInterface $route_builder, LockBackendInterface $lock) {
-    $this->routeBuilder = $route_builder;
-    $this->lock = $lock;
-  }
+  protected $menuLinkManager;
 
   /**
-   * Rebuilds routers if necessary.
+   * Constructs the MenuRouterRebuildSubscriber object.
    *
-   * @param \Symfony\Component\HttpKernel\Event\PostResponseEvent $event
-   *   The event object.
+   * @param \Drupal\Core\Lock\LockBackendInterface $lock
+   *   The lock backend.
+   * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager
+   *   The menu link plugin manager.
    */
-  public function onKernelTerminate(PostResponseEvent $event) {
-    $this->routeBuilder->rebuildIfNeeded();
+  public function __construct(LockBackendInterface $lock, MenuLinkManagerInterface $menu_link_manager) {
+    $this->lock = $lock;
+    $this->menuLinkManager = $menu_link_manager;
   }
 
   /**
@@ -73,11 +68,9 @@ protected function menuLinksRebuild() {
       $transaction = db_transaction();
       try {
         // Ensure the menu links are up to date.
-        menu_link_rebuild_defaults();
-        // Clear the menu cache.
-        menu_cache_clear_all();
-        // Track which menu items are expanded.
-        _menu_update_expanded_menus();
+        $this->menuLinkManager->rebuild();
+        // Ignore any database replicas temporarily.
+        db_ignore_replica();
       }
       catch (\Exception $e) {
         $transaction->rollback();
@@ -95,13 +88,9 @@ protected function menuLinksRebuild() {
   }
 
   /**
-   * Registers the methods in this class that should be listeners.
-   *
-   * @return array
-   *   An array of event listener definitions.
+   * {@inheritdoc}
    */
   static function getSubscribedEvents() {
-    $events[KernelEvents::TERMINATE][] = array('onKernelTerminate', 200);
     $events[RoutingEvents::FINISHED][] = array('onRouterRebuild', 200);
     return $events;
   }
diff --git a/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php
index 87fdb87..584b141 100644
--- a/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php
@@ -17,7 +17,7 @@
 use Symfony\Component\HttpKernel\KernelEvents;
 
 /**
- * Rebuilds the default menu links and runs menu-specific code if necessary.
+ * Rebuilds the router if needed at the end of the request.
  */
 class RouterRebuildSubscriber implements EventSubscriberInterface {
 
@@ -27,21 +27,13 @@ class RouterRebuildSubscriber implements EventSubscriberInterface {
   protected $routeBuilder;
 
   /**
-   * @var \Drupal\Core\Lock\LockBackendInterface
-   */
-  protected $lock;
-
-  /**
    * Constructs the RouterRebuildSubscriber object.
    *
    * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
    *   The route builder.
-   * @param \Drupal\Core\Lock\LockBackendInterface $lock
-   *   The lock backend.
    */
-  public function __construct(RouteBuilderInterface $route_builder, LockBackendInterface $lock) {
+  public function __construct(RouteBuilderInterface $route_builder) {
     $this->routeBuilder = $route_builder;
-    $this->lock = $lock;
   }
 
   /**
@@ -55,54 +47,10 @@ public function onKernelTerminate(PostResponseEvent $event) {
   }
 
   /**
-   * Rebuilds the menu links and deletes the local_task cache tag.
-   *
-   * @param \Symfony\Component\EventDispatcher\Event $event
-   *   The event object.
-   */
-  public function onRouterRebuild(Event $event) {
-    $this->menuLinksRebuild();
-    Cache::deleteTags(array('local_task' => 1));
-  }
-
-  /**
-   * Perform menu-specific rebuilding.
-   */
-  protected function menuLinksRebuild() {
-    if ($this->lock->acquire(__FUNCTION__)) {
-      $transaction = db_transaction();
-      try {
-        // Ensure the menu links are up to date.
-        menu_link_rebuild_defaults();
-        // Clear the menu cache.
-        menu_cache_clear_all();
-        // Track which menu items are expanded.
-        _menu_update_expanded_menus();
-      }
-      catch (\Exception $e) {
-        $transaction->rollback();
-        watchdog_exception('menu', $e);
-      }
-
-      $this->lock->release(__FUNCTION__);
-    }
-    else {
-      // Wait for another request that is already doing this work.
-      // We choose to block here since otherwise the router item may not
-      // be available during routing resulting in a 404.
-      $this->lock->wait(__FUNCTION__);
-    }
-  }
-
-  /**
-   * Registers the methods in this class that should be listeners.
-   *
-   * @return array
-   *   An array of event listener definitions.
+   * {@inheritdoc}
    */
   static function getSubscribedEvents() {
     $events[KernelEvents::TERMINATE][] = array('onKernelTerminate', 200);
-    $events[RoutingEvents::FINISHED][] = array('onRouterRebuild', 200);
     return $events;
   }
 
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkBase.php b/core/lib/Drupal/Core/Menu/MenuLinkBase.php
index 43cb49a..125bbf0 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkBase.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkBase.php
@@ -40,22 +40,6 @@ public function getWeight() {
   /**
    * {@inheritdoc}
    */
-  public function getTitle() {
-    // Subclasses may pull in the request or specific attributes as parameters.
-    $options = array();
-    if (!empty($this->pluginDefinition['title_context'])) {
-      $options['context'] = $this->pluginDefinition['title_context'];
-    }
-    $args = array();
-    if (isset($this->pluginDefinition['title_arguments']) && $title_arguments = $this->pluginDefinition['title_arguments']) {
-      $args = (array) $title_arguments;
-    }
-    return $this->t($this->pluginDefinition['title'], $args, $options);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function getMenuName() {
     return $this->pluginDefinition['menu_name'];
   }
@@ -112,16 +96,6 @@ public function isDeletable() {
   /**
    * {@inheritdoc}
    */
-  public function getDescription() {
-    if ($this->pluginDefinition['description']) {
-      return $this->t($this->pluginDefinition['description']);
-    }
-    return '';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function getOptions() {
     return $this->pluginDefinition['options'] ?: array();
   }
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkDefault.php b/core/lib/Drupal/Core/Menu/MenuLinkDefault.php
index cf14df7..5d0f8e2 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkDefault.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkDefault.php
@@ -66,6 +66,32 @@ public static function create(ContainerInterface $container, array $configuratio
   /**
    * {@inheritdoc}
    */
+  public function getTitle() {
+    // Subclasses may pull in the request or specific attributes as parameters.
+    $options = array();
+    if (!empty($this->pluginDefinition['title_context'])) {
+      $options['context'] = $this->pluginDefinition['title_context'];
+    }
+    $args = array();
+    if (isset($this->pluginDefinition['title_arguments']) && $title_arguments = $this->pluginDefinition['title_arguments']) {
+      $args = (array) $title_arguments;
+    }
+    return $this->t($this->pluginDefinition['title'], $args, $options);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    if ($this->pluginDefinition['description']) {
+      return $this->t($this->pluginDefinition['description']);
+    }
+    return '';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function isResettable() {
     // The link can be reset if it has an override.
     return (bool) $this->staticOverride->loadOverride($this->getPluginId());
@@ -77,11 +103,13 @@ public function isResettable() {
   public function updateLink(array $new_definition_values, $persist) {
     // Filter the list of updates to only those that are allowed.
     $overrides = array_intersect_key($new_definition_values, $this->overrideAllowed);
+    // Update the definition.
+    $this->pluginDefinition = $overrides + $this->getPluginDefinition();
     if ($persist) {
+      // Always save the menu name as an override to avoid defaulting to tools.
+      $overrides['menu_name'] = $this->pluginDefinition['menu_name'];
       $this->staticOverride->saveOverride($this->getPluginId(), $overrides);
     }
-    // Update the definition.
-    $this->pluginDefinition = $overrides + $this->getPluginDefinition();
     return $this->pluginDefinition;
   }
 
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkTree.php b/core/lib/Drupal/Core/Menu/MenuLinkTree.php
index 8d09c2c..e5d54fe 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkTree.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkTree.php
@@ -8,7 +8,9 @@
 namespace Drupal\Core\Menu;
 
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Controller\ControllerResolverInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Routing\RouteProviderInterface;
 
 /**
@@ -45,6 +47,30 @@ class MenuLinkTree implements MenuLinkTreeInterface {
   protected $controllerResolver;
 
   /**
+   * The cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cache;
+
+  /**
+   * The current route match.
+   *
+   * @var \Drupal\Core\Routing\RouteMatchInterface
+   */
+  protected $routeMatch;
+
+  /**
+   * Stores the cached current route parameters by menu and current route match.
+   *
+   * @todo Remove this non-static caching in
+   *   https://www.drupal.org/node/1805054.
+   *
+   * @var \Drupal\Core\Menu\MenuTreeParameters[]
+   */
+  protected $cachedCurrentRouteParameters;
+
+  /**
    * Constructs a \Drupal\Core\Menu\MenuLinkTree object.
    *
    * @param \Drupal\Core\Menu\MenuTreeStorageInterface $tree_storage
@@ -57,31 +83,53 @@ class MenuLinkTree implements MenuLinkTreeInterface {
    *   The active menu trail service.
    * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
    *   The controller resolver.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
+   *   The cache backend.
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The current route match.
    */
-  public function __construct(MenuTreeStorageInterface $tree_storage, MenuLinkManagerInterface $menu_link_manager, RouteProviderInterface $route_provider, MenuActiveTrailInterface $menu_active_trail, ControllerResolverInterface $controller_resolver) {
+  public function __construct(MenuTreeStorageInterface $tree_storage, MenuLinkManagerInterface $menu_link_manager, RouteProviderInterface $route_provider, MenuActiveTrailInterface $menu_active_trail, ControllerResolverInterface $controller_resolver, CacheBackendInterface $cache, RouteMatchInterface $route_match) {
     $this->treeStorage = $tree_storage;
     $this->menuLinkManager = $menu_link_manager;
     $this->routeProvider = $route_provider;
     $this->menuActiveTrail = $menu_active_trail;
     $this->controllerResolver = $controller_resolver;
+    // @todo Remove these two in https://www.drupal.org/node/1805054.
+    $this->cache = $cache;
+    $this->routeMatch = $route_match;
   }
 
   /**
    * {@inheritdoc}
    */
   public function getCurrentRouteMenuTreeParameters($menu_name) {
-    $active_trail = $this->menuActiveTrail->getActiveTrailIds($menu_name);
-
-    $parameters = new MenuTreeParameters();
-    $parameters->setActiveTrail($active_trail)
-      // We want links in the active trail to be expanded.
-      ->addExpandedParents($active_trail)
-      // We marked the links in the active trail to be expanded, but we also
-      // want their descendants that have the "expanded" flag enabled to be
-      // expanded.
-      ->addExpandedParents($this->treeStorage->getExpanded($menu_name, $active_trail));
-
-    return $parameters;
+    $route_parameters = $this->routeMatch->getRawParameters()->all();
+    ksort($route_parameters);
+    $cid = 'current-route-parameters:' . $menu_name . ':route:' . $this->routeMatch->getRouteName() . ':route_parameters:' . serialize($route_parameters);
+
+    if (!isset($this->cachedCurrentRouteParameters[$menu_name])) {
+      $cache = $this->cache->get($cid);
+      if ($cache && $cache->data) {
+        $parameters = $cache->data;
+      }
+      else {
+        $active_trail = $this->menuActiveTrail->getActiveTrailIds($menu_name);
+
+        $parameters = new MenuTreeParameters();
+        $parameters->setActiveTrail($active_trail)
+          // We want links in the active trail to be expanded.
+          ->addExpandedParents($active_trail)
+          // We marked the links in the active trail to be expanded, but we also
+          // want their descendants that have the "expanded" flag enabled to be
+          // expanded.
+          ->addExpandedParents($this->treeStorage->getExpanded($menu_name, $active_trail));
+
+        $this->cache->set($cid, $parameters, CacheBackendInterface::CACHE_PERMANENT, array('menu' => $menu_name));
+      }
+      $this->cachedCurrentRouteParameters[$menu_name] = $parameters;
+    }
+
+    return $this->cachedCurrentRouteParameters[$menu_name];
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php
index 4912a05..d3093ac 100644
--- a/core/lib/Drupal/Core/Utility/LinkGenerator.php
+++ b/core/lib/Drupal/Core/Utility/LinkGenerator.php
@@ -99,7 +99,9 @@ public function generateFromUrl($text, Url $url) {
       // drupal.active-link library know the path in a standardized manner.
       if (!isset($variables['options']['attributes']['data-drupal-link-system-path'])) {
         // @todo System path is deprecated - use the route name and parameters.
-        $variables['options']['attributes']['data-drupal-link-system-path'] = $url->getInternalPath();
+        $system_path = $url->getInternalPath();
+        // Special case for the front page.
+        $variables['options']['attributes']['data-drupal-link-system-path'] = $system_path == '' ? '<front>' : $system_path;
       }
     }
 
diff --git a/core/modules/book/book.admin.inc b/core/modules/book/book.admin.inc
index 9a55869..6a3edc1 100644
--- a/core/modules/book/book.admin.inc
+++ b/core/modules/book/book.admin.inc
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\book\BookManager;
 use Drupal\Core\Render\Element;
 
 /**
@@ -87,7 +88,7 @@ function theme_book_admin_table($variables) {
         'subgroup' => 'book-pid',
         'source' => 'book-nid',
         'hidden' => TRUE,
-        'limit' => MENU_MAX_DEPTH - 2,
+        'limit' => BookManager::BOOK_MAX_DEPTH - 2,
       ),
       array(
         'action' => 'order',
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index ba00049..1177ebe 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -5,6 +5,7 @@
  * Allows users to create and organize related content in an outline.
  */
 
+use Drupal\book\BookManager;
 use Drupal\book\BookManagerInterface;
 use Drupal\Component\Utility\String;
 use Drupal\Core\Entity\EntityInterface;
@@ -123,7 +124,7 @@ function book_node_links_alter(array &$node_links, NodeInterface $node, array &$
       if ($context['view_mode'] == 'full' && node_is_page($node)) {
         $child_type = \Drupal::config('book.settings')->get('child_type');
         $access_controller = \Drupal::entityManager()->getAccessController('node');
-        if (($account->hasPermission('add content to books') || $account->hasPermission('administer book outlines')) && $access_controller->createAccess($child_type) && $node->isPublished() && $node->book['depth'] < MENU_MAX_DEPTH) {
+        if (($account->hasPermission('add content to books') || $account->hasPermission('administer book outlines')) && $access_controller->createAccess($child_type) && $node->isPublished() && $node->book['depth'] < BookManager::BOOK_MAX_DEPTH) {
           $links['book_add_child'] = array(
             'title' => t('Add child page'),
             'href' => 'node/add/' . $child_type,
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 4fbd597..21144a5 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -203,9 +203,9 @@ function content_translation_entity_operation_alter(array &$operations, \Drupal\
 }
 
 /**
- * Implements hook_menu_link_defaults_alter().
+ * Implements hook_menu_links_discovered_alter().
  */
-function content_translation_menu_link_defaults_alter(array &$links) {
+function content_translation_menu_links_discovered_alter(array &$links) {
   // Clarify where translation settings are located.
   $links['language.content_settings_page']['title'] = 'Content language and translation';
   $links['language.content_settings_page']['description'] = 'Configure language and translation support for content.';
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationSettingsTest.php b/core/modules/content_translation/src/Tests/ContentTranslationSettingsTest.php
index 556e419..76fa6be 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationSettingsTest.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationSettingsTest.php
@@ -44,7 +44,7 @@ function setUp() {
    * Tests that the settings UI works as expected.
    */
   function testSettingsUI() {
-    // Check for the content_translation_menu_link_defaults_alter() changes.
+    // Check for the content_translation_menu_links_discovered_alter() changes.
     $this->drupalGet('admin/config');
     $this->assertLink('Content language and translation');
     $this->assertText('Configure language and translation support for content.');
diff --git a/core/modules/dblog/dblog.module b/core/modules/dblog/dblog.module
index d6e20ff..b206879 100644
--- a/core/modules/dblog/dblog.module
+++ b/core/modules/dblog/dblog.module
@@ -37,9 +37,9 @@ function dblog_help($route_name, RouteMatchInterface $route_match) {
 }
 
 /**
- * Implements hook_menu_link_defaults_alter().
+ * Implements hook_menu_links_discovered_alter().
  */
-function dblog_menu_link_defaults_alter(&$links) {
+function dblog_menu_links_discovered_alter(&$links) {
   if (\Drupal::moduleHandler()->moduleExists('search')) {
     $links['dblog.search'] = array(
       'title' => 'Top search phrases',
diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module
index 5aa7929..40c1eb7 100644
--- a/core/modules/editor/editor.module
+++ b/core/modules/editor/editor.module
@@ -40,12 +40,12 @@ function editor_help($route_name, RouteMatchInterface $route_match) {
 }
 
 /**
- * Implements hook_menu_link_defaults_alter().
+ * Implements hook_menu_links_discovered_alter().
  *
  * Rewrites the menu entries for filter module that relate to the configuration
  * of text editors.
  */
-function editor_menu_link_defaults_alter(array &$links) {
+function editor_menu_links_discovered_alter(array &$links) {
   $links['filter.admin_overview']['title'] = 'Text formats and editors';
   $links['filter.admin_overview']['description'] = 'Configure how user-contributed content is filtered and formatted, as well as the text editor user interface (WYSIWYGs or toolbars).';
 }
diff --git a/core/modules/entity/src/Controller/EntityDisplayModeController.php b/core/modules/entity/src/Controller/EntityDisplayModeController.php
index f0db942..af31044 100644
--- a/core/modules/entity/src/Controller/EntityDisplayModeController.php
+++ b/core/modules/entity/src/Controller/EntityDisplayModeController.php
@@ -8,6 +8,7 @@
 namespace Drupal\entity\Controller;
 
 use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Url;
 
 /**
  * Provides methods for entity display mode routes.
@@ -26,7 +27,7 @@ public function viewModeTypeSelection() {
       if ($entity_type->isFieldable() && $entity_type->hasViewBuilderClass()) {
         $entity_types[$entity_type_id] = array(
           'title' => $entity_type->getLabel(),
-          'link_path' => 'admin/structure/display-modes/view/add/' . $entity_type_id,
+          'url' => new Url('entity.view_mode_add_type', array('entity_type_id' => $entity_type_id)),
           'localized_options' => array(),
         );
       }
@@ -49,7 +50,7 @@ public function formModeTypeSelection() {
       if ($entity_type->isFieldable() && $entity_type->hasFormClasses()) {
         $entity_types[$entity_type_id] = array(
           'title' => $entity_type->getLabel(),
-          'link_path' => 'admin/structure/display-modes/form/add/' . $entity_type_id,
+          'url' => new Url('entity.form_mode_add_type', array('entity_type_id' => $entity_type_id)),
           'localized_options' => array(),
         );
       }
diff --git a/core/modules/help/src/Controller/HelpController.php b/core/modules/help/src/Controller/HelpController.php
index 26a3160..437c3a8 100644
--- a/core/modules/help/src/Controller/HelpController.php
+++ b/core/modules/help/src/Controller/HelpController.php
@@ -125,8 +125,7 @@ public function helpPage($name) {
       if (!empty($admin_tasks)) {
         $links = array();
         foreach ($admin_tasks as $task) {
-          $link = $task['localized_options'];
-          $link['href'] = $task['link_path'];
+          $link = $task['url']->toArray();
           $link['title'] = $task['title'];
           $links[] = $link;
         }
diff --git a/core/modules/language/src/Tests/LanguageConfigSchemaTest.php b/core/modules/language/src/Tests/LanguageConfigSchemaTest.php
index e4862bc..28fcad0 100644
--- a/core/modules/language/src/Tests/LanguageConfigSchemaTest.php
+++ b/core/modules/language/src/Tests/LanguageConfigSchemaTest.php
@@ -24,7 +24,7 @@ class LanguageConfigSchemaTest extends WebTestBase {
    *
    * @var array
    */
-  public static $modules = array('language');
+  public static $modules = array('language', 'menu_link_content');
 
   /**
    * A user with administrative permissions.
@@ -55,8 +55,8 @@ function testValidLanguageConfigSchema() {
     $settings_path = 'admin/config/regional/content-language';
 
     // Enable translation for menu link.
-    $edit['entity_types[menu_link]'] = TRUE;
-    $edit['settings[menu_link][menu_link][settings][language][language_show]'] = TRUE;
+    $edit['entity_types[menu_link_content]'] = TRUE;
+    $edit['settings[menu_link_content][menu_link_content][settings][language][language_show]'] = TRUE;
 
     // Enable translation for user.
     $edit['entity_types[user]'] = TRUE;
@@ -67,7 +67,7 @@ function testValidLanguageConfigSchema() {
 
     $config_data = \Drupal::config('language.settings')->get();
     // Make sure configuration saved correctly.
-    $this->assertTrue($config_data['entities']['menu_link']['menu_link']['language']['default_configuration']['language_show']);
+    $this->assertTrue($config_data['entities']['menu_link_content']['menu_link_content']['language']['default_configuration']['language_show']);
 
     $this->assertConfigSchema(\Drupal::service('config.typed'), 'language.settings', $config_data);
   }
diff --git a/core/modules/locale/src/Tests/LocaleLocaleLookupTest.php b/core/modules/locale/src/Tests/LocaleLocaleLookupTest.php
index b25d639..4f4c3d5 100644
--- a/core/modules/locale/src/Tests/LocaleLocaleLookupTest.php
+++ b/core/modules/locale/src/Tests/LocaleLocaleLookupTest.php
@@ -22,7 +22,7 @@ class LocaleLocaleLookupTest extends WebTestBase {
    *
    * @var array
    */
-  public static $modules = array('locale', 'menu_link');
+  public static $modules = array('locale');
 
   /**
    * Tests hasTranslation().
diff --git a/core/modules/menu_link/menu_link.api.php b/core/modules/menu_link/menu_link.api.php
deleted file mode 100644
index 4702436..0000000
--- a/core/modules/menu_link/menu_link.api.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-
-/**
- * @file
- * Hooks provided by the Menu link module.
- */
-
-/**
- * @addtogroup hooks
- * @{
- */
-
-/**
- * Alter a menu link after it has been translated and before it is rendered.
- *
- * This hook is invoked from _menu_link_translate() after a menu link has been
- * translated; i.e., after the user access to the link's target page has
- * been checked. It is only invoked if $menu_link['options']['alter'] has been
- * set to a non-empty value (e.g. TRUE). This flag should be set using
- * hook_ENTITY_TYPE_presave() for entity 'menu_link'.
- *
- * Implementations of this hook are able to alter any property of the menu link.
- * For example, this hook may be used to add a page-specific query string to all
- * menu links, or hide a certain link by setting:
- * @code
- *   'hidden' => 1,
- * @endcode
- *
- * @param \Drupal\menu_link\Entity\MenuLink $menu_link
- *   A menu link entity.
- *
- * @see hook_menu_link_alter()
- */
-function hook_translated_menu_link_alter(\Drupal\menu_link\Entity\MenuLink &$menu_link, $map) {
-  if ($menu_link->href == 'devel/cache/clear') {
-    $menu_link->localized_options['query'] = drupal_get_destination();
-  }
-}
-
-/**
- * @} End of "addtogroup hooks".
- */
diff --git a/core/modules/menu_link/menu_link.info.yml b/core/modules/menu_link/menu_link.info.yml
deleted file mode 100644
index bc8e62b..0000000
--- a/core/modules/menu_link/menu_link.info.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-name: Menu Link
-type: module
-description: Provides menu links.
-package: Core
-version: VERSION
-core: 8.x
-# @todo Menu links functionality has been moved from system.module and menu.inc
-# to this module, so make it required until everything is moved over.
-required: TRUE
diff --git a/core/modules/menu_link/menu_link.install b/core/modules/menu_link/menu_link.install
deleted file mode 100644
index c789d34..0000000
--- a/core/modules/menu_link/menu_link.install
+++ /dev/null
@@ -1,221 +0,0 @@
-<?php
-
-/**
- * @file
- * Install, update and uninstall functions for the menu_link module.
- */
-
-/**
- * Implements hook_schema().
- */
-function menu_link_schema() {
-  $schema['menu_links'] = array(
-    'description' => 'Contains the individual links within a menu.',
-    'fields' => array(
-     'menu_name' => array(
-        'description' => "The menu name. All links with the same menu name (such as 'tools') are part of the same menu.",
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'mlid' => array(
-        'description' => 'The menu link ID (mlid) is the integer primary key.',
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'uuid' => array(
-        'description' => 'Unique Key: Universally unique identifier for this entity.',
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => FALSE,
-      ),
-      'machine_name' => array(
-        'description' => 'Unique machine name: Optional human-readable ID for this link.',
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => FALSE,
-      ),
-      'plid' => array(
-        'description' => 'The parent link ID (plid) is the mlid of the link above in the hierarchy, or zero if the link is at the top level in its menu.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'link_path' => array(
-        'description' => 'The Drupal path or external path this link points to.',
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'langcode' => array(
-        'description' => 'The {language}.langcode of this link.',
-        'type' => 'varchar',
-        'length' => 12,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'link_title' => array(
-        'description' => 'The text displayed for the link.',
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'options' => array(
-        'description' => 'A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.',
-        'type' => 'blob',
-        'not null' => FALSE,
-        'serialize' => TRUE,
-      ),
-      'module' => array(
-        'description' => 'The name of the module that generated this link.',
-        'type' => 'varchar',
-        'length' => DRUPAL_EXTENSION_NAME_MAX_LENGTH,
-        'not null' => TRUE,
-        'default' => 'system',
-      ),
-      'hidden' => array(
-        'description' => 'A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link)',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'small',
-      ),
-      'external' => array(
-        'description' => 'A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'small',
-      ),
-      'has_children' => array(
-        'description' => 'Flag indicating whether any links have this link as a parent (1 = children exist, 0 = no children).',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'small',
-      ),
-      'expanded' => array(
-        'description' => 'Flag for whether this link should be rendered as expanded in menus - expanded links always have their child links displayed, instead of only when the link is in the active trail (1 = expanded, 0 = not expanded)',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'small',
-      ),
-      'weight' => array(
-        'description' => 'Link weight among links in the same menu at the same depth.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'depth' => array(
-        'description' => 'The depth relative to the top level. A link with plid == 0 will have depth == 1.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'small',
-      ),
-      'customized' => array(
-        'description' => 'A flag to indicate that the user has manually created or edited the link (1 = customized, 0 = not customized).',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'small',
-      ),
-      'p1' => array(
-        'description' => 'The first mlid in the materialized path. If N = depth, then pN must equal the mlid. If depth > 1 then p(N-1) must equal the plid. All pX where X > depth must equal zero. The columns p1 .. p9 are also called the parents.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p2' => array(
-        'description' => 'The second mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p3' => array(
-        'description' => 'The third mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p4' => array(
-        'description' => 'The fourth mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p5' => array(
-        'description' => 'The fifth mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p6' => array(
-        'description' => 'The sixth mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p7' => array(
-        'description' => 'The seventh mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p8' => array(
-        'description' => 'The eighth mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'p9' => array(
-        'description' => 'The ninth mlid in the materialized path. See p1.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'updated' => array(
-        'description' => 'Flag that indicates that this link was generated during the update from Drupal 5.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'small',
-      ),
-      'route_name' => array(
-        'description' => 'The machine name of a defined Symfony Route this menu item represents.',
-        'type' => 'varchar',
-        'length' => 255,
-      ),
-      'route_parameters' => array(
-        'description' => 'Serialized array of route parameters of this menu link.',
-        'type' => 'blob',
-        'size' => 'big',
-        'not null' => FALSE,
-        'serialize' => TRUE,
-      ),
-    ),
-    'indexes' => array(
-      'path_menu' => array(array('link_path', 128), 'menu_name'),
-      'menu_plid_expand_child' => array('menu_name', 'plid', 'expanded', 'has_children'),
-      'menu_parents' => array('menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
-    ),
-    'primary key' => array('mlid'),
-  );
-
-  return $schema;
-}
diff --git a/core/modules/menu_link/menu_link.module b/core/modules/menu_link/menu_link.module
deleted file mode 100644
index 8a2ce38..0000000
--- a/core/modules/menu_link/menu_link.module
+++ /dev/null
@@ -1,225 +0,0 @@
-<?php
-
-/**
- * @file
- * Enables users to create menu links.
- */
-
-use Drupal\Core\Routing\RouteMatchInterface;
-use Drupal\Core\Url;
-use Drupal\menu_link\Entity\MenuLink;
-use Drupal\menu_link\MenuLinkInterface;
-
-function menu_link_help($route_name, RouteMatchInterface $route_match) {
-  switch ($route_name) {
-    case 'help.page.menu_link':
-      $output = '';
-      $output .= '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('The Menu Link module allows users to create menu links. It is required by the Menu UI module, which provides an interface for managing menus. See the <a href="!menu-help">Menu UI module help page</a> for more information.', array('!menu-help' => \Drupal::url('help.page', array('name' => 'menu_ui')))) . '</p>';
-      return $output;
-  }
-}
-
-/**
- * Entity URI callback.
- *
- * @param \Drupal\menu_link\Entity\MenuLink $menu_link
- *   A menu link entity.
- */
-function menu_link_uri(MenuLink $menu_link) {
-  return new Url($menu_link->route_name, $menu_link->route_parameters);
-}
-
-/**
- * Loads a menu link entity.
- *
- * This function should never be called from within node_load() or any other
- * function used as a menu object load function since an infinite recursion may
- * occur.
- *
- * @param int $mlid
- *   The menu link ID.
- * @param bool $reset
- *   (optional) Whether to reset the menu_link_load_multiple() cache.
- *
- * @return \Drupal\menu_link\Entity\MenuLink|null
- *   A menu link entity, or NULL if there is no entity with the given ID.
- *
- * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
- *   Use \Drupal\menu_link\Entity\MenuLink::load().
- */
-function menu_link_load($mlid = NULL, $reset = FALSE) {
-  if ($reset) {
-    \Drupal::entityManager()->getStorage('menu_link')->resetCache(array($mlid));
-  }
-  return MenuLink::load($mlid);
-}
-
-/**
- * Loads menu link entities from the database.
- *
- * @param array $mlids
- *   (optional) An array of entity IDs. If omitted, all entities are loaded.
- * @param bool $reset
- *   (optional) Whether to reset the internal cache.
- *
- * @return array<\Drupal\menu_link\Entity\MenuLink>
- *   An array of menu link entities indexed by entity IDs.
- *
- * @see menu_link_load()
- * @see entity_load_multiple()
- *
- * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
- *   Use \Drupal\menu_link\Entity\MenuLink::loadMultiple().
- */
-function menu_link_load_multiple(array $mlids = NULL, $reset = FALSE) {
-  if ($reset) {
-    \Drupal::entityManager()->getStorage('menu_link')->resetCache($mlids);
-  }
-  return MenuLink::loadMultiple($mlids);
-}
-
-/**
- * Deletes a menu link.
- *
- * @param int $mlid
- *   The menu link ID.
- *
- * @see menu_link_delete_multiple()
- */
-function menu_link_delete($mlid) {
-  menu_link_delete_multiple(array($mlid));
-}
-
-/**
- * Deletes multiple menu links.
- *
- * @param array $mlids
- *   An array of menu link IDs.
- * @param bool $force
- *   (optional) Forces deletion. Internal use only, setting to TRUE is
- *   discouraged. Defaults to FALSE.
- * @param bool $prevent_reparenting
- *   (optional) Disables the re-parenting logic from the deletion process.
- *   Defaults to FALSE.
- */
-function menu_link_delete_multiple(array $mlids, $force = FALSE, $prevent_reparenting = FALSE) {
-  if (!$mlids) {
-    // If no IDs or invalid IDs were passed, do nothing.
-    return;
-  }
-
-  $controller = \Drupal::entityManager()
-    ->getStorage('menu_link');
-  if (!$force) {
-    $entity_query = \Drupal::entityQuery('menu_link');
-    $group = $entity_query->orConditionGroup()
-      ->condition('module', 'system', '<>')
-      ->condition('updated', 0, '<>');
-
-    $entity_query->condition('mlid', $mlids, 'IN');
-    $entity_query->condition($group);
-
-    $result = $entity_query->execute();
-    $entities = $controller->loadMultiple($result);
-  }
-  else {
-    $entities = $controller->loadMultiple($mlids);
-  }
-  $controller->setPreventReparenting($prevent_reparenting);
-  $controller->delete($entities);
-}
-
-/**
- * Saves a menu link.
- *
- * After calling this function, rebuild the menu cache using
- * menu_cache_clear_all().
- *
- * @param \Drupal\menu_link\Entity\MenuLink $menu_link
- *   The menu link entity to be saved.
- *
- * @return int|bool
- *   Returns SAVED_NEW or SAVED_UPDATED if the save operation succeeded, or
- *   FALSE if it failed.
- */
-function menu_link_save(MenuLink $menu_link) {
-  return $menu_link->save();
-}
-
-/**
- * Inserts, updates, enables, disables, or deletes an uncustomized menu link.
- *
- * @param string $module
- *   The name of the module that owns the link.
- * @param string $op
- *   Operation to perform: insert, update, enable, disable, or delete.
- * @param string $link_path
- *   The path this link points to.
- * @param string $link_title
- *   (optional) Title of the link to insert or new title to update the link to.
- *   Unused for delete. Defaults to NULL.
- *
- * @return integer|null
- *   The insert op returns the mlid of the new item. Others op return NULL.
- */
-function menu_link_maintain($module, $op, $link_path, $link_title = NULL) {
-  $menu_link_controller = \Drupal::entityManager()
-    ->getStorage('menu_link');
-  switch ($op) {
-    case 'insert':
-      $menu_link = entity_create('menu_link', array(
-        'link_title' => $link_title,
-        'link_path' => $link_path,
-        'module' => $module,)
-      );
-      return $menu_link->save();
-
-    case 'update':
-      $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => $link_path, 'module' => $module, 'customized' => 0));
-      foreach ($menu_links as $menu_link) {
-        $menu_link->original = clone $menu_link;
-        if (isset($link_title)) {
-          $menu_link->link_title = $link_title;
-        }
-        $menu_link_controller->save($menu_link);
-      }
-      break;
-
-    case 'enable':
-    case 'disable':
-      $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => $link_path, 'module' => $module, 'customized' => 0));
-      foreach ($menu_links as $menu_link) {
-        $menu_link->original = clone $menu_link;
-        $menu_link->hidden = ($op == 'disable' ? 1 : 0);
-        $menu_link->customized = 1;
-        if (isset($link_title)) {
-          $menu_link->link_title = $link_title;
-        }
-        $menu_link_controller->save($menu_link);
-      }
-      break;
-
-    case 'delete':
-      $result = \Drupal::entityQuery('menu_link')->condition('link_path', $link_path)->execute();
-      if (!empty($result)) {
-        menu_link_delete_multiple($result);
-      }
-      break;
-  }
-}
-
-/**
- * Implements hook_system_breadcrumb_alter().
- */
-function menu_link_system_breadcrumb_alter(array &$breadcrumb, RouteMatchInterface $route_match, array $context) {
-  // Custom breadcrumb behavior for editing menu links, we append a link to
-  // the menu in which the link is found.
-  if (($route_match->getRouteName() == 'menu_ui.link_edit') && $menu_link = $route_match->getParameter('menu_link')) {
-    if (($menu_link instanceof MenuLinkInterface) && !$menu_link->isNew()) {
-      // Add a link to the menu admin screen.
-      $menu = entity_load('menu', $menu_link->menu_name);
-      $breadcrumb[] = Drupal::l($menu->label(), 'menu_ui.menu_edit', array('menu' => $menu->id));
-    }
-  }
-}
diff --git a/core/modules/menu_link/menu_link.services.yml b/core/modules/menu_link/menu_link.services.yml
deleted file mode 100644
index 88f5037..0000000
--- a/core/modules/menu_link/menu_link.services.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-services:
-  menu_link.tree:
-    class: Drupal\menu_link\MenuTree
-    arguments: ['@database', '@cache.data', '@language_manager', '@request_stack', '@entity.manager', '@entity.query', '@state']
-  menu_link.static:
-    class: Drupal\menu_link\StaticMenuLinks
-    arguments: ['@module_handler']
diff --git a/core/modules/menu_link/src/Entity/MenuLink.php b/core/modules/menu_link/src/Entity/MenuLink.php
deleted file mode 100644
index e2215fd..0000000
--- a/core/modules/menu_link/src/Entity/MenuLink.php
+++ /dev/null
@@ -1,680 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\Entity\MenuLink.
- */
-
-namespace Drupal\menu_link\Entity;
-
-use Drupal\Component\Utility\NestedArray;
-use Drupal\Component\Utility\UrlHelper;
-use Drupal\Core\Cache\Cache;
-use Drupal\Core\Entity\Entity;
-use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Url;
-use Drupal\menu_link\MenuLinkInterface;
-use Symfony\Component\Routing\Route;
-
-/**
- * Defines the menu link entity class.
- *
- * @EntityType(
- *   id = "menu_link",
- *   label = @Translation("Menu link"),
- *   controllers = {
- *     "storage" = "Drupal\menu_link\MenuLinkStorage",
- *     "access" = "Drupal\menu_link\MenuLinkAccessController",
- *     "form" = {
- *       "default" = "Drupal\menu_link\MenuLinkForm"
- *     }
- *   },
- *   admin_permission = "administer menu",
- *   static_cache = FALSE,
- *   base_table = "menu_links",
- *   uri_callback = "menu_link_uri",
- *   translatable = TRUE,
- *   entity_keys = {
- *     "id" = "mlid",
- *     "label" = "link_title",
- *     "uuid" = "uuid",
- *     "bundle" = "bundle"
- *   },
- * )
- */
-class MenuLink extends Entity implements \ArrayAccess, MenuLinkInterface {
-
-  /**
-   * The link's menu name.
-   *
-   * @var string
-   */
-  public $menu_name = 'tools';
-
-  /**
-   * The link's bundle.
-   *
-   * @var string
-   */
-  public $bundle = 'tools';
-
-  /**
-   * The menu link ID.
-   *
-   * @var int
-   */
-  public $mlid;
-
-  /**
-   * An optional machine name if defined via the menu_link.static service.
-   *
-   * @var string
-   */
-  public $machine_name;
-
-  /**
-   * The menu link UUID.
-   *
-   * @var string
-   */
-  public $uuid;
-
-  /**
-   * The parent link ID.
-   *
-   * @var int
-   */
-  public $plid;
-
-  /**
-   * The Drupal path or external path this link points to.
-   *
-   * @var string
-   */
-  public $link_path;
-
-  /**
-   * The entity label.
-   *
-   * @var string
-   */
-  public $link_title = '';
-
-  /**
-   * A serialized array of options to be passed to the url() or l() function,
-   * such as a query string or HTML attributes.
-   *
-   * @var array
-   */
-  public $options = array();
-
-  /**
-   * The name of the module that generated this link.
-   *
-   * @var string
-   */
-  public $module = 'menu_ui';
-
-  /**
-   * A flag for whether the link should be rendered in menus.
-   *
-   * @var int
-   */
-  public $hidden = 0;
-
-  /**
-   * A flag to indicate if the link points to a full URL starting with a
-   * protocol, like http:// (1 = external, 0 = internal).
-   *
-   * @var int
-   */
-  public $external;
-
-  /**
-   * Flag indicating whether any links have this link as a parent.
-   *
-   * @var int
-   */
-  public $has_children = 0;
-
-  /**
-   * Flag for whether this link should be rendered as expanded in menus.
-   * Expanded links always have their child links displayed, instead of only
-   * when the link is in the active trail.
-   *
-   * @var int
-   */
-  public $expanded = 0;
-
-  /**
-   * Link weight among links in the same menu at the same depth.
-   *
-   * @var int
-   */
-  public $weight = 0;
-
-  /**
-   * The depth relative to the top level. A link with plid == 0 will have
-   * depth == 1.
-   *
-   * @var int
-   */
-  public $depth;
-
-  /**
-   * A flag to indicate that the user has manually created or edited the link.
-   *
-   * @var int
-   */
-  public $customized = 0;
-
-  /**
-   * The first entity ID in the materialized path.
-   *
-   * @var int
-   *
-   * @todo Investigate whether the p1, p2, .. pX properties can be moved to a
-   * single array property.
-   */
-  public $p1;
-
-  /**
-   * The second entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p2;
-
-  /**
-   * The third entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p3;
-
-  /**
-   * The fourth entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p4;
-
-  /**
-   * The fifth entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p5;
-
-  /**
-   * The sixth entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p6;
-
-  /**
-   * The seventh entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p7;
-
-  /**
-   * The eighth entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p8;
-
-  /**
-   * The ninth entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p9;
-
-  /**
-   * The menu link modification timestamp.
-   *
-   * @var int
-   */
-  public $updated = 0;
-
-  /**
-   * The name of the route associated with this menu link, if any.
-   *
-   * @var string
-   */
-  public $route_name;
-
-  /**
-   * The parameters of the route associated with this menu link, if any.
-   *
-   * @var array
-   */
-  public $route_parameters = array();
-
-  /**
-   * The route object associated with this menu link, if any.
-   *
-   * @var \Symfony\Component\Routing\Route
-   */
-  protected $routeObject;
-
-  /**
-   * Boolean indicating whether a new revision should be created on save.
-   *
-   * @var bool
-   */
-  protected $newRevision = FALSE;
-
-  /**
-   * Indicates whether this is the default revision.
-   *
-   * @var bool
-   */
-  protected $isDefaultRevision = TRUE;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setNewRevision($value = TRUE) {
-    $this->newRevision = $value;
-  }
-  /**
-   * {@inheritdoc}
-   */
-  public function isNewRevision() {
-    return $this->newRevision || ($this->getEntityType()->hasKey('revision') && !$this->getRevisionId());
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getRevisionId() {
-    return NULL;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function isTranslatable() {
-    // @todo Inject the entity manager and retrieve bundle info from it.
-    $bundles = entity_get_bundles($this->entityTypeId);
-    return !empty($bundles[$this->bundle()]['translatable']);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
-  }
-
-  /**
-   * Overrides Entity::id().
-   */
-  public function id() {
-    return $this->mlid;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function bundle() {
-    return $this->bundle;
-  }
-
-  /**
-   * Overrides Entity::createDuplicate().
-   */
-  public function createDuplicate() {
-    $duplicate = parent::createDuplicate();
-    $duplicate->plid = NULL;
-    return $duplicate;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getRoute() {
-    if (!$this->route_name) {
-      return NULL;
-    }
-    if (!($this->routeObject instanceof Route)) {
-      $route_provider = \Drupal::service('router.route_provider');
-      $this->routeObject = $route_provider->getRouteByName($this->route_name);
-    }
-    return $this->routeObject;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setRouteObject(Route $route) {
-    $this->routeObject = $route;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function reset() {
-    // To reset the link to its original values, we need to retrieve its
-    // definition from the menu_link.static service. Otherwise, for example,
-    // the link's menu would not be reset, because properties like the original
-    // 'menu_name' are not stored anywhere else. Since resetting a link happens
-    // rarely and this is a one-time operation, retrieving the full set of
-    // default menu links does little harm.
-    $all_links = \Drupal::service('menu_link.static')->getLinks();
-    $original = $all_links[$this->machine_name];
-    $original['machine_name'] = $this->machine_name;
-    /** @var \Drupal\menu_link\MenuLinkStorageInterface $storage */
-    $storage = \Drupal::entityManager()->getStorage($this->entityTypeId);
-    // @todo Do not create a new entity in order to update it, see
-    //   https://drupal.org/node/2241865
-    $new_link = $storage->createFromDefaultLink($original);
-    $new_link->setOriginalId($this->id());
-    // Allow the menu to be determined by the parent
-    if (!empty($new_link['parent']) && !empty($all_links[$new_link['parent']])) {
-      // Walk up the tree to find the menu name.
-      $parent = $all_links[$new_link['parent']];
-      $existing_parent = db_select('menu_links')
-        ->fields('menu_links')
-        ->condition('machine_name', $parent['machine_name'])
-        ->execute()->fetchAssoc();
-      if ($existing_parent) {
-        /** @var \Drupal\Core\Entity\EntityInterface $existing_parent */
-        $existing_parent = $storage->create($existing_parent);
-        $new_link->menu_name = $existing_parent->menu_name;
-        $new_link->plid = $existing_parent->id();
-      }
-    }
-    // Merge existing menu link's ID and 'has_children' property.
-    foreach (array('mlid', 'has_children') as $key) {
-      $new_link->{$key} = $this->{$key};
-    }
-    $new_link->save();
-    return $new_link;
-  }
-
-  /**
-   * Implements ArrayAccess::offsetExists().
-   */
-  public function offsetExists($offset) {
-    return isset($this->{$offset});
-  }
-
-  /**
-   * Implements ArrayAccess::offsetGet().
-   */
-  public function &offsetGet($offset) {
-    return $this->{$offset};
-  }
-
-  /**
-   * Implements ArrayAccess::offsetSet().
-   */
-  public function offsetSet($offset, $value) {
-    $this->{$offset} = $value;
-  }
-
-  /**
-   * Implements ArrayAccess::offsetUnset().
-   */
-  public function offsetUnset($offset) {
-    unset($this->{$offset});
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function preDelete(EntityStorageInterface $storage, array $entities) {
-    parent::preDelete($storage, $entities);
-
-    // Nothing to do if we don't want to reparent children.
-    if ($storage->getPreventReparenting()) {
-      return;
-    }
-
-    foreach ($entities as $entity) {
-      // Children get re-attached to the item's parent.
-      if ($entity->has_children) {
-        $children = $storage->loadByProperties(array('plid' => $entity->plid));
-        foreach ($children as $child) {
-          $child->plid = $entity->plid;
-          $storage->save($child);
-        }
-      }
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function postDelete(EntityStorageInterface $storage, array $entities) {
-    parent::postDelete($storage, $entities);
-
-    // Update the has_children status of the parent.
-    foreach ($entities as $entity) {
-      if (!$storage->getPreventReparenting()) {
-        $storage->updateParentalStatus($entity);
-      }
-    }
-
-    // Also clear the menu system static caches.
-    menu_reset_static_cache();
-    _menu_update_expanded_menus();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function preSave(EntityStorageInterface $storage) {
-    parent::preSave($storage);
-
-    // This is the easiest way to handle the unique internal path '<front>',
-    // since a path marked as external does not need to match a route.
-    $this->external = (UrlHelper::isExternal($this->link_path) || $this->link_path == '<front>') ? 1 : 0;
-
-    // Try to find a parent link. If found, assign it and derive its menu.
-    $parent = $this->findParent($storage);
-    if ($parent) {
-      $this->plid = $parent->id();
-      $this->menu_name = $parent->menu_name;
-    }
-    // If no corresponding parent link was found, move the link to the top-level.
-    else {
-      $this->plid = 0;
-    }
-
-    // Directly fill parents for top-level links.
-    if ($this->plid == 0) {
-      $this->p1 = $this->id();
-      for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) {
-        $parent_property = "p$i";
-        $this->{$parent_property} = 0;
-      }
-      $this->depth = 1;
-    }
-    // Otherwise, ensure that this link's depth is not beyond the maximum depth
-    // and fill parents based on the parent link.
-    else {
-      if ($this->has_children && $this->original) {
-        $limit = MENU_MAX_DEPTH - $storage->findChildrenRelativeDepth($this->original) - 1;
-      }
-      else {
-        $limit = MENU_MAX_DEPTH - 1;
-      }
-      if ($parent->depth > $limit) {
-        return FALSE;
-      }
-      $this->depth = $parent->depth + 1;
-      $this->setParents($parent);
-    }
-
-    // Need to check both plid and menu_name, since plid can be 0 in any menu.
-    if (isset($this->original) && ($this->plid != $this->original->plid || $this->menu_name != $this->original->menu_name)) {
-      $storage->moveChildren($this);
-    }
-
-    // Find the route_name.
-    if (!$this->external && !isset($this->route_name)) {
-      $url = Url::createFromPath($this->link_path);
-      $this->route_name = $url->getRouteName();
-      $this->route_parameters = $url->getRouteParameters();
-    }
-    elseif (empty($this->link_path)) {
-      $this->link_path = \Drupal::urlGenerator()->getPathFromRoute($this->route_name, $this->route_parameters);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
-    parent::postSave($storage, $update);
-
-    // Check the has_children status of the parent.
-    $storage->updateParentalStatus($this);
-
-
-    // Entity::postSave() calls Entity::invalidateTagsOnSave(), which only
-    // handles the regular cases. The MenuLink entity has two special cases.
-    $cache_tags = array();
-    // Case 1: a newly created menu link is *also* added to a menu, so we must
-    // invalidate the associated menu's cache tag.
-    if (!$update) {
-      $cache_tags = $this->getCacheTag();
-    }
-    // Case 2: a menu link may be moved from one menu to another; the original
-    // menu's cache tag must also be invalidated.
-    if (isset($this->original) && $this->menu_name != $this->original->menu_name) {
-      $cache_tags = NestedArray::mergeDeep($cache_tags, $this->original->getCacheTag());
-    }
-    Cache::invalidateTags($cache_tags);
-
-    // Also clear the menu system static caches.
-    menu_reset_static_cache();
-
-    // Track which menu items are expanded.
-    _menu_update_expanded_menus();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function postLoad(EntityStorageInterface $storage, array &$entities) {
-    parent::postLoad($storage, $entities);
-
-    $routes = array();
-    foreach ($entities as $menu_link) {
-      $menu_link->options = unserialize($menu_link->options);
-      $menu_link->route_parameters = unserialize($menu_link->route_parameters);
-
-      // By default use the menu_name as type.
-      $menu_link->bundle = $menu_link->menu_name;
-
-      // For all links that have an associated route, load the route object now
-      // and save it on the object. That way we avoid a select N+1 problem later.
-      if ($menu_link->route_name) {
-        $routes[$menu_link->id()] = $menu_link->route_name;
-      }
-    }
-
-    // Now mass-load any routes needed and associate them.
-    if ($routes) {
-      $route_objects = \Drupal::service('router.route_provider')->getRoutesByNames($routes);
-      foreach ($routes as $entity_id => $route) {
-        // Not all stored routes will be valid on load.
-        if (isset($route_objects[$route])) {
-          $entities[$entity_id]->setRouteObject($route_objects[$route]);
-        }
-      }
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setParents(MenuLinkInterface $parent) {
-    $i = 1;
-    while ($i < $this->depth) {
-      $p = 'p' . $i++;
-      $this->{$p} = $parent->{$p};
-    }
-    $p = 'p' . $i++;
-    // The parent (p1 - p9) corresponding to the depth always equals the mlid.
-    $this->{$p} = $this->id();
-    while ($i <= MENU_MAX_DEPTH) {
-      $p = 'p' . $i++;
-      $this->{$p} = 0;
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function findParent(EntityStorageInterface $storage) {
-    $parent = FALSE;
-
-    // This item is explicitly top-level, skip the rest of the parenting.
-    if (isset($this->plid) && empty($this->plid)) {
-      return $parent;
-    }
-
-    // If we have a parent link ID, try to use that.
-    $candidates = array();
-    if (isset($this->plid)) {
-      $candidates[] = $this->plid;
-    }
-
-    // Else, if we have a link hierarchy try to find a valid parent in there.
-    if (!empty($this->depth) && $this->depth > 1) {
-      for ($depth = $this->depth - 1; $depth >= 1; $depth--) {
-        $parent_property = "p$depth";
-        $candidates[] = $this->$parent_property;
-      }
-    }
-
-    foreach ($candidates as $mlid) {
-      $parent = $storage->load($mlid);
-      if ($parent) {
-        break;
-      }
-    }
-    return $parent;
-  }
-
-  /**
-   * Builds and returns the renderable array for this menu link.
-   *
-   * @return array
-   *   A renderable array representing the content of the link.
-   */
-  public function build() {
-    $build = array(
-      '#type' => 'link',
-      '#title' => $this->title,
-      '#href' => $this->href,
-      '#route_name' => $this->route_name ? $this->route_name : NULL,
-      '#route_parameters' => $this->route_parameters,
-      '#options' => !empty($this->localized_options) ? $this->localized_options : array(),
-    );
-    return $build;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getCacheTag() {
-    return entity_load('menu', $this->menu_name)->getCacheTag();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getListCacheTags() {
-    return entity_load('menu', $this->menu_name)->getListCacheTags();
-  }
-
-}
diff --git a/core/modules/menu_link/src/MenuLinkAccessController.php b/core/modules/menu_link/src/MenuLinkAccessController.php
deleted file mode 100644
index c6e6b91..0000000
--- a/core/modules/menu_link/src/MenuLinkAccessController.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\MenuLinkAccessController.
- */
-
-namespace Drupal\menu_link;
-
-use Drupal\Core\Entity\EntityAccessController;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Session\AccountInterface;
-
-/**
- * Defines an access controller for the menu link entity.
- *
- * @see \Drupal\menu_link\Entity\MenuLink
- */
-class MenuLinkAccessController extends EntityAccessController {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
-    $access = $account->hasPermission('administer menu');
-    if ($access) {
-      switch ($operation) {
-        case 'reset':
-          // Reset allowed for items defined via hook_menu() and customized.
-          return !empty($entity->machine_name) && $entity->customized;
-
-        case 'delete':
-          // Only items created by the Menu UI module can be deleted.
-          return $entity->module == 'menu_ui' || $entity->updated == 1;
-
-      }
-    }
-    return $access;
-  }
-
-}
diff --git a/core/modules/menu_link/src/MenuLinkForm.php b/core/modules/menu_link/src/MenuLinkForm.php
deleted file mode 100644
index f5cbca1..0000000
--- a/core/modules/menu_link/src/MenuLinkForm.php
+++ /dev/null
@@ -1,311 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\MenuLinkForm.
- */
-
-namespace Drupal\menu_link;
-
-use Drupal\Component\Utility\UrlHelper;
-use Drupal\Core\Entity\EntityForm;
-use Drupal\Core\Language\LanguageInterface;
-use Drupal\Core\Path\AliasManagerInterface;
-use Drupal\Core\Routing\UrlGenerator;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Form controller for the node edit forms.
- */
-class MenuLinkForm extends EntityForm {
-
-  /**
-   * The menu link storage.
-   *
-   * @var \Drupal\menu_link\MenuLinkStorageInterface
-   */
-  protected $menuLinkStorage;
-
-  /**
-   * The path alias manager.
-   *
-   * @var \Drupal\Core\Path\AliasManagerInterface
-   */
-  protected $pathAliasManager;
-
-  /**
-   * The URL generator.
-   *
-   * @var \Drupal\Core\Routing\UrlGenerator
-   */
-  protected $urlGenerator;
-
-  /**
-   * Constructs a new MenuLinkForm object.
-   *
-   * @param \Drupal\menu_link\MenuLinkStorageInterface $menu_link_storage
-   *   The menu link storage.
-   * @param \Drupal\Core\Path\AliasManagerInterface $path_alias_manager
-   *   The path alias manager.
-   * @param \Drupal\Core\Routing\UrlGenerator $url_generator
-   *   The URL generator.
-   */
-  public function __construct(MenuLinkStorageInterface $menu_link_storage, AliasManagerInterface $path_alias_manager, UrlGenerator $url_generator) {
-    $this->menuLinkStorage = $menu_link_storage;
-    $this->pathAliasManager = $path_alias_manager;
-    $this->urlGenerator = $url_generator;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('entity.manager')->getStorage('menu_link'),
-      $container->get('path.alias_manager'),
-      $container->get('url_generator')
-    );
-  }
-
-  /**
-   * Overrides EntityForm::form().
-   */
-  public function form(array $form, array &$form_state) {
-    $menu_link = $this->entity;
-    // Since menu_link_load() no longer returns a translated and access checked
-    // item, do it here instead.
-    _menu_link_translate($menu_link);
-
-    $form['link_title'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Menu link title'),
-      '#default_value' => $menu_link->link_title,
-      '#description' => t('The text to be used for this link in the menu.'),
-      '#required' => TRUE,
-    );
-    foreach (array('link_path', 'mlid', 'module', 'has_children', 'options') as $key) {
-      $form[$key] = array('#type' => 'value', '#value' => $menu_link->{$key});
-    }
-    // Any item created or edited via this interface is considered "customized".
-    $form['customized'] = array('#type' => 'value', '#value' => 1);
-
-    // We are not using url() when constructing this path because it would add
-    // $base_path.
-    $path = $menu_link->link_path;
-    if (isset($menu_link->options['query'])) {
-      $path .= '?' . $this->urlGenerator->httpBuildQuery($menu_link->options['query']);
-    }
-    if (isset($menu_link->options['fragment'])) {
-      $path .= '#' . $menu_link->options['fragment'];
-    }
-    if ($menu_link->module == 'menu_ui') {
-      $form['link_path'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Path'),
-        '#maxlength' => 255,
-        '#default_value' => $path,
-        '#description' => t('The path for this menu link. This can be an internal Drupal path such as %add-node or an external URL such as %drupal. Enter %front to link to the front page.', array('%front' => '<front>', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org')),
-        '#required' => TRUE,
-      );
-    }
-    else {
-      $form['_path'] = array(
-        '#type' => 'item',
-        '#title' => t('Path'),
-        '#description' => l($menu_link->link_title, $menu_link->href, $menu_link->options),
-      );
-    }
-
-    $form['description'] = array(
-      '#type' => 'textarea',
-      '#title' => t('Description'),
-      '#default_value' => isset($menu_link->options['attributes']['title']) ? $menu_link->options['attributes']['title'] : '',
-      '#rows' => 1,
-      '#description' => t('Shown when hovering over the menu link.'),
-    );
-    $form['enabled'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Enabled'),
-      '#default_value' => !$menu_link->hidden,
-      '#description' => t('Menu links that are not enabled will not be listed in any menu.'),
-    );
-    $form['expanded'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Show as expanded'),
-      '#default_value' => $menu_link->expanded,
-      '#description' => t('If selected and this menu link has children, the menu will always appear expanded.'),
-    );
-
-    // Generate a list of possible parents (not including this link or descendants).
-    $options = menu_ui_parent_options(menu_ui_get_menus(), $menu_link);
-    $default = $menu_link->menu_name . ':' . $menu_link->plid;
-    if (!isset($options[$default])) {
-      $default = 'tools:0';
-    }
-    $form['parent'] = array(
-      '#type' => 'select',
-      '#title' => t('Parent link'),
-      '#default_value' => $default,
-      '#options' => $options,
-      '#description' => t('The maximum depth for a link and all its children is fixed at !maxdepth. Some menu links may not be available as parents if selecting them would exceed this limit.', array('!maxdepth' => MENU_MAX_DEPTH)),
-      '#attributes' => array('class' => array('menu-title-select')),
-    );
-
-    // Get number of items in menu so the weight selector is sized appropriately.
-    $delta = $this->menuLinkStorage->countMenuLinks($menu_link->menu_name);
-    $form['weight'] = array(
-      '#type' => 'weight',
-      '#title' => t('Weight'),
-      // Old hardcoded value.
-      '#delta' => max($delta, 50),
-      '#default_value' => $menu_link->weight,
-      '#description' => t('Optional. In the menu, the heavier links will sink and the lighter links will be positioned nearer the top.'),
-    );
-
-    // Language module allows to configure the menu link language independently
-    // of the menu language. It also allows to optionally show the language
-    // selector on the menu link form so that the language of each menu link can
-    // be configured individually.
-    if ($this->moduleHandler->moduleExists('language')) {
-      $language_configuration = language_get_default_configuration('menu_link', $menu_link->bundle());
-      $default_langcode = ($menu_link->isNew() ? $language_configuration['langcode'] : $menu_link->langcode);
-      $language_show = $language_configuration['language_show'];
-    }
-    // Without Language module menu links inherit the menu language and no
-    // language selector is shown.
-    else {
-      $default_langcode = ($menu_link->isNew() ? entity_load('menu', $menu_link->menu_name)->language()->getId() : $menu_link->langcode);
-      $language_show = FALSE;
-    }
-
-    $form['langcode'] = array(
-      '#type' => 'language_select',
-      '#title' => t('Language'),
-      '#languages' => LanguageInterface::STATE_ALL,
-      '#default_value' => $default_langcode,
-      '#access' => $language_show,
-    );
-
-    return parent::form($form, $form_state, $menu_link);
-  }
-
-  /**
-   * Overrides EntityForm::actions().
-   */
-  protected function actions(array $form, array &$form_state) {
-    $element = parent::actions($form, $form_state);
-    $element['submit']['#button_type'] = 'primary';
-    return $element;
-  }
-
-  /**
-   * Overrides EntityForm::validate().
-   */
-  public function validate(array $form, array &$form_state) {
-    $menu_link = $this->buildEntity($form, $form_state);
-
-    $normal_path = $this->pathAliasManager->getPathByAlias($menu_link->link_path);
-    if ($menu_link->link_path != $normal_path) {
-      drupal_set_message(t('The menu system stores system paths only, but will use the URL alias for display. %link_path has been stored as %normal_path', array('%link_path' => $menu_link->link_path, '%normal_path' => $normal_path)));
-      $menu_link->link_path = $normal_path;
-      $form_state['values']['link_path'] = $normal_path;
-    }
-    if (!UrlHelper::isExternal($menu_link->link_path)) {
-      $parsed_link = parse_url($menu_link->link_path);
-      if (isset($parsed_link['query'])) {
-        $menu_link->options['query'] = array();
-        parse_str($parsed_link['query'], $menu_link->options['query']);
-      }
-      else {
-        // Use unset() rather than setting to empty string
-        // to avoid redundant serialized data being stored.
-        unset($menu_link->options['query']);
-      }
-      if (isset($parsed_link['fragment'])) {
-        $menu_link->options['fragment'] = $parsed_link['fragment'];
-      }
-      else {
-        unset($menu_link->options['fragment']);
-      }
-      if (isset($parsed_link['path']) && $menu_link->link_path != $parsed_link['path']) {
-        $menu_link->link_path = $parsed_link['path'];
-      }
-    }
-    if (!trim($menu_link->link_path) || !drupal_valid_path($menu_link->link_path, TRUE)) {
-      $this->setFormError('link_path', $form_state, $this->t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $menu_link->link_path)));
-    }
-
-    parent::validate($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildEntity(array $form, array &$form_state) {
-    // @todo: Remove this when menu links are converted to content entities in
-    //   http://drupal.org/node/1842858.
-    $entity = clone $this->entity;
-    // If you submit a form, the form state comes from caching, which forces
-    // the controller to be the one before caching. Ensure to have the
-    // controller of the current request.
-    $form_state['controller'] = $this;
-
-    // Copy top-level form values to entity properties, without changing
-    // existing entity properties that are not being edited by
-    // this form.
-    foreach ($form_state['values'] as $key => $value) {
-      $entity->$key = $value;
-    }
-
-    // Invoke all specified builders for copying form values to entity properties.
-    if (isset($form['#entity_builders'])) {
-      foreach ($form['#entity_builders'] as $function) {
-        call_user_func_array($function, array($entity->getEntityTypeId(), $entity, &$form, &$form_state));
-      }
-    }
-
-    return $entity;
-  }
-
-  /**
-   * Overrides EntityForm::submit().
-   */
-  public function submit(array $form, array &$form_state) {
-    // Build the menu link object from the submitted values.
-    $menu_link = parent::submit($form, $form_state);
-
-    // The value of "hidden" is the opposite of the value supplied by the
-    // "enabled" checkbox.
-    $menu_link->hidden = (int) !$menu_link->enabled;
-    unset($menu_link->enabled);
-
-    $menu_link->options['attributes']['title'] = $menu_link->description;
-    list($menu_link->menu_name, $menu_link->plid) = explode(':', $menu_link->parent);
-
-    return $menu_link;
-  }
-
-  /**
-   * Overrides EntityForm::save().
-   */
-  public function save(array $form, array &$form_state) {
-    $menu_link = $this->entity;
-
-    $saved = $menu_link->save();
-
-    if ($saved) {
-      drupal_set_message(t('The menu link has been saved.'));
-      $form_state['redirect_route'] = array(
-        'route_name' => 'menu_ui.menu_edit',
-        'route_parameters' => array(
-          'menu' => $menu_link->menu_name,
-        ),
-      );
-    }
-    else {
-      drupal_set_message(t('There was an error saving the menu link.'), 'error');
-      $form_state['rebuild'] = TRUE;
-    }
-  }
-
-}
diff --git a/core/modules/menu_link/src/MenuLinkInterface.php b/core/modules/menu_link/src/MenuLinkInterface.php
deleted file mode 100644
index fdfab67..0000000
--- a/core/modules/menu_link/src/MenuLinkInterface.php
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\MenuLinkInterface.
- */
-
-namespace Drupal\menu_link;
-
-use Drupal\Core\Entity\EntityInterface;
-use Symfony\Component\Routing\Route;
-
-/**
- * Provides an interface defining a menu link entity.
- */
-interface MenuLinkInterface extends EntityInterface {
-
-  /**
-   * Returns the Route object associated with this link, if any.
-   *
-   * @return \Symfony\Component\Routing\Route|null
-   *   The route object for this menu link, or NULL if there isn't one.
-   */
-  public function getRoute();
-
-  /**
-   * Sets the route object for this link.
-   *
-   * This should only be called by MenuLinkStorage when loading
-   * the link object. Calling it at other times could result in unpredictable
-   * behavior.
-   *
-   * @param \Symfony\Component\Routing\Route $route
-   */
-  public function setRouteObject(Route $route);
-
-  /**
-   * Resets a system-defined menu link.
-   *
-   * @return \Drupal\menu_link\MenuLinkInterface
-   *   A menu link entity.
-   */
-  public function reset();
-
-}
diff --git a/core/modules/menu_link/src/MenuLinkStorage.php b/core/modules/menu_link/src/MenuLinkStorage.php
deleted file mode 100644
index 1ece00d..0000000
--- a/core/modules/menu_link/src/MenuLinkStorage.php
+++ /dev/null
@@ -1,319 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\MenuLinkStorage.
- */
-
-namespace Drupal\menu_link;
-
-use Drupal\Core\Entity\EntityDatabaseStorage;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityStorageException;
-
-/**
- * Controller class for menu links.
- *
- * This extends the Drupal\entity\EntityDatabaseStorage class, adding
- * required special handling for menu_link entities.
- */
-class MenuLinkStorage extends EntityDatabaseStorage implements MenuLinkStorageInterface {
-
-  /**
-   * Indicates whether the delete operation should re-parent children items.
-   *
-   * @var bool
-   */
-  protected $preventReparenting = FALSE;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function create(array $values = array()) {
-    // The bundle of menu links being the menu name is not enforced but is the
-    // default behavior if no bundle is set.
-    if (!isset($values['bundle']) && isset($values['menu_name'])) {
-      $values['bundle'] = $values['menu_name'];
-    }
-    return parent::create($values);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function save(EntityInterface $entity) {
-
-    // We return SAVED_UPDATED by default because the logic below might not
-    // update the entity if its values haven't changed, so returning FALSE
-    // would be confusing in that situation.
-    $return = SAVED_UPDATED;
-
-    $transaction = $this->database->startTransaction();
-    try {
-      // Load the stored entity, if any.
-      if (!$entity->isNew() && !isset($entity->original)) {
-        $id = $entity->id();
-        if ($entity->getOriginalId() !== NULL) {
-          $id = $entity->getOriginalId();
-        }
-        $entity->original = $this->loadUnchanged($id);
-      }
-
-      if ($entity->isNew()) {
-        $entity->mlid = $this->database->insert($this->entityType->getBaseTable())->fields(array('menu_name' => $entity->menu_name))->execute();
-        $entity->enforceIsNew();
-      }
-
-      // Unlike the save() method from EntityDatabaseStorage, we invoke the
-      // 'presave' hook first because we want to allow modules to alter the
-      // entity before all the logic from our preSave() method.
-      $this->invokeHook('presave', $entity);
-      $entity->preSave($this);
-
-      // If every value in $entity->original is the same in the $entity, there
-      // is no reason to run the update queries or clear the caches. We use
-      // array_intersect_key() with the $entity as the first parameter because
-      // $entity may have additional keys left over from building a router entry.
-      // The intersect removes the extra keys, allowing a meaningful comparison.
-      if ($entity->isNew() || (array_intersect_key(get_object_vars($entity), get_object_vars($entity->original)) != get_object_vars($entity->original))) {
-        $return = drupal_write_record($this->entityType->getBaseTable(), $entity, $this->idKey);
-
-        if ($return) {
-          if (!$entity->isNew()) {
-            $this->resetCache(array($entity->{$this->idKey}));
-            $entity->postSave($this, TRUE);
-            $this->invokeHook('update', $entity);
-          }
-          else {
-            $return = SAVED_NEW;
-            $this->resetCache();
-
-            $entity->enforceIsNew(FALSE);
-            $entity->postSave($this, FALSE);
-            $this->invokeHook('insert', $entity);
-          }
-        }
-      }
-
-      // Ignore replica server temporarily.
-      db_ignore_replica();
-      unset($entity->original);
-
-      return $return;
-    }
-    catch (\Exception $e) {
-      $transaction->rollback();
-      watchdog_exception($this->entityTypeId, $e);
-      throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setPreventReparenting($value = FALSE) {
-    $this->preventReparenting = $value;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getPreventReparenting() {
-    return $this->preventReparenting;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function loadUpdatedCustomized(array $router_paths) {
-    $query = parent::buildQuery(NULL);
-    $query
-      ->condition(db_or()
-      ->condition('updated', 1)
-      ->condition(db_and()
-        ->condition('router_path', $router_paths, 'NOT IN')
-        ->condition('external', 0)
-        ->condition('customized', 1)
-        )
-      );
-    $query_result = $query->execute();
-
-    // We provide the necessary arguments for PDO to create objects of the
-    // specified entity class.
-    // @see \Drupal\Core\Entity\EntityInterface::__construct()
-    $query_result->setFetchMode(\PDO::FETCH_CLASS, $this->entityClass, array(array(), $this->entityTypeId));
-
-    return $query_result->fetchAllAssoc($this->idKey);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function loadModuleAdminTasks() {
-    // @todo - this code will move out of the menu link entity, so we are doing
-    //   a straight SQL query for expediency.
-    $result = $this->database->select('menu_links');
-    $result->condition('machine_name', 'system.admin');
-    $result->addField('menu_links', 'mlid');
-    $plid = $result->execute()->fetchField();
-
-    $query = $this->database->select('menu_links', 'base', array('fetch' => \PDO::FETCH_ASSOC));
-    $query->fields('base');
-    $query
-      ->condition('base.hidden', 0, '>=')
-      ->condition('base.module', '', '>')
-      ->condition('base.machine_name', '', '>')
-      ->condition('base.p1', $plid);
-    $entities = $query->execute()->fetchAll();
-
-    return $entities;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE) {
-    // If plid == 0, there is nothing to update.
-    if ($entity->plid) {
-      // Check if at least one visible child exists in the table.
-      $query = $this->getQuery();
-      $query
-        ->condition('menu_name', $entity->menu_name)
-        ->condition('hidden', 0)
-        ->condition('plid', $entity->plid)
-        ->count();
-
-      if ($exclude) {
-        $query->condition('mlid', $entity->id(), '<>');
-      }
-
-      $parent_has_children = ((bool) $query->execute()) ? 1 : 0;
-      $this->database->update('menu_links')
-        ->fields(array('has_children' => $parent_has_children))
-        ->condition('mlid', $entity->plid)
-        ->execute();
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function findChildrenRelativeDepth(EntityInterface $entity) {
-    // @todo Since all we need is a specific field from the base table, does it
-    // make sense to convert to EFQ?
-    $query = $this->database->select('menu_links');
-    $query->addField('menu_links', 'depth');
-    $query->condition('menu_name', $entity->menu_name);
-    $query->orderBy('depth', 'DESC');
-    $query->range(0, 1);
-
-    $i = 1;
-    $p = 'p1';
-    while ($i <= MENU_MAX_DEPTH && $entity->{$p}) {
-      $query->condition($p, $entity->{$p});
-      $p = 'p' . ++$i;
-    }
-
-    $max_depth = $query->execute()->fetchField();
-
-    return ($max_depth > $entity->depth) ? $max_depth - $entity->depth : 0;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function moveChildren(EntityInterface $entity) {
-    $query = $this->database->update($this->entityType->getBaseTable());
-
-    $query->fields(array('menu_name' => $entity->menu_name));
-
-    $p = 'p1';
-    $expressions = array();
-    for ($i = 1; $i <= $entity->depth; $p = 'p' . ++$i) {
-      $expressions[] = array($p, ":p_$i", array(":p_$i" => $entity->{$p}));
-    }
-    $j = $entity->original->depth + 1;
-    while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) {
-      $expressions[] = array('p' . $i++, 'p' . $j++, array());
-    }
-    while ($i <= MENU_MAX_DEPTH) {
-      $expressions[] = array('p' . $i++, 0, array());
-    }
-
-    $shift = $entity->depth - $entity->original->depth;
-    if ($shift > 0) {
-      // The order of expressions must be reversed so the new values don't
-      // overwrite the old ones before they can be used because "Single-table
-      // UPDATE assignments are generally evaluated from left to right"
-      // @see http://dev.mysql.com/doc/refman/5.0/en/update.html
-      $expressions = array_reverse($expressions);
-    }
-    foreach ($expressions as $expression) {
-      $query->expression($expression[0], $expression[1], $expression[2]);
-    }
-
-    $query->expression('depth', 'depth + :depth', array(':depth' => $shift));
-    $query->condition('menu_name', $entity->original->menu_name);
-    $p = 'p1';
-    for ($i = 1; $i <= MENU_MAX_DEPTH && $entity->original->{$p}; $p = 'p' . ++$i) {
-      $query->condition($p, $entity->original->{$p});
-    }
-
-    $query->execute();
-
-    // Check the has_children status of the parent, while excluding this item.
-    $this->updateParentalStatus($entity->original, TRUE);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function countMenuLinks($menu_name) {
-    $query = $this->getQuery();
-    $query
-      ->condition('menu_name', $menu_name)
-      ->count();
-    return $query->execute();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getParentFromHierarchy(EntityInterface $entity) {
-    $parent_path = $entity->link_path;
-    do {
-      $parent = FALSE;
-      $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
-
-      $query = $this->getQuery();
-      $query
-        ->condition('mlid', $entity->id(), '<>')
-        ->condition('module', 'system')
-        // We always respect the link's 'menu_name'; inheritance for router
-        // items is ensured in _menu_router_build().
-        ->condition('menu_name', $entity->menu_name)
-        ->condition('link_path', $parent_path);
-
-      $result = $query->execute();
-      // Only valid if we get a unique result.
-      if (count($result) == 1) {
-        $parent = $this->load(reset($result));
-      }
-    } while ($parent === FALSE && $parent_path);
-
-    return $parent;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function createFromDefaultLink(array $item) {
-    // Suggested items are disabled by default.
-    $item += array(
-      'hidden' => 0,
-      'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])),
-    );
-    return $this->create($item);
-  }
-
-}
diff --git a/core/modules/menu_link/src/MenuLinkStorageInterface.php b/core/modules/menu_link/src/MenuLinkStorageInterface.php
deleted file mode 100644
index 3d3fd5d..0000000
--- a/core/modules/menu_link/src/MenuLinkStorageInterface.php
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\MenuLinkStorageInterface.
-*/
-
-namespace Drupal\menu_link;
-
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityStorageInterface;
-
-/**
- * Defines a common interface for menu link entity controller classes.
- */
-interface MenuLinkStorageInterface extends EntityStorageInterface {
-
-  /**
-   * Sets an internal flag that allows us to prevent the reparenting operations
-   * executed during deletion.
-   *
-   * @param bool $value
-   *   TRUE if reparenting should be allowed, FALSE if it should be prevented.
-   */
-  public function setPreventReparenting($value = FALSE);
-
-  /**
-   * Gets value of internal flag that allows/prevents reparenting operations
-   * executed during deletion.
-   *
-   * @return bool
-   *   TRUE if reparenting is allowed, FALSE if it is prevented.
-   */
-  public function getPreventReparenting();
-
-  /**
-   * Loads system menu link as needed by system_get_module_admin_tasks().
-   *
-   * @return array
-   *   An array of menu link entities indexed by their IDs.
-   */
-  public function loadModuleAdminTasks();
-
-  /**
-   * Checks and updates the 'has_children' property for the parent of a link.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
-   */
-  public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE);
-
-  /**
-   * Finds the depth of an item's children relative to its depth.
-   *
-   * For example, if the item has a depth of 2 and the maximum of any child in
-   * the menu link tree is 5, the relative depth is 3.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
-   *
-   * @return int
-   *   The relative depth, or zero.
-   */
-  public function findChildrenRelativeDepth(EntityInterface $entity);
-
-  /**
-   * Updates the children of a menu link that is being moved.
-   *
-   * The menu name, parents (p1 - p6), and depth are updated for all children of
-   * the link, and the has_children status of the previous parent is updated.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
-   */
-  public function moveChildren(EntityInterface $entity);
-
-  /**
-   * Returns the number of menu links from a menu.
-   *
-   * @param string $menu_name
-   *   The unique name of a menu.
-   */
-  public function countMenuLinks($menu_name);
-
-  /**
-   * Tries to derive menu link's parent from the path hierarchy.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
-   *
-   * @return \Drupal\Core\Entity\EntityInterface|false
-   *   A menu link entity or FALSE if not valid parent was found.
-   */
-  public function getParentFromHierarchy(EntityInterface $entity);
-
-  /**
-   * Builds a menu link entity from a default item.
-   *
-   * This function should only be called for link data from
-   * the menu_link.static service.
-   *
-   * @param array $item
-   *   An item returned from the menu_link.static service.
-   *
-   * @return \Drupal\menu_link\MenuLinkInterface
-   *   A menu link entity.
-   */
-  public function createFromDefaultLink(array $item);
-
-}
diff --git a/core/modules/menu_link/src/MenuTree.php b/core/modules/menu_link/src/MenuTree.php
deleted file mode 100644
index adbf85d..0000000
--- a/core/modules/menu_link/src/MenuTree.php
+++ /dev/null
@@ -1,619 +0,0 @@
-<?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\State\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 $languageManager;
-
-  /**
-   * The request stack.
-   *
-   * @var \Symfony\Component\HttpFoundation\RequestStack
-   */
-  protected $requestStack;
-
-  /**
-   * The menu link storage.
-   *
-   * @var \Drupal\menu_link\MenuLinkStorageInterface
-   */
-  protected $menuLinkStorage;
-
-  /**
-   * The entity query factory.
-   *
-   * @var \Drupal\Core\Entity\Query\QueryFactory
-   */
-  protected $queryFactory;
-
-  /**
-   * The state.
-   *
-   * @var \Drupal\Core\State\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 tree used by the doBuildTree method, 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 variable distinct from static::$menuTree by having also items without
-   * access by the current user.
-   *
-   * 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\State\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->getStorage('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 && $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 $this->doBuildTree()).
-      $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();
-
-    // 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.
-      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 && $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,
-          );
-          $active_trail = $this->getActiveTrailIds($menu_name);
-
-          // If this page is accessible to the current user, build the tree
-          // parameters accordingly.
-          if ($page_not_403) {
-            // The active trail contains more than only array(0 => 0).
-            if (count($active_trail) > 1) {
-              // 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 getActiveTrailIds($menu_name) {
-    // 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);
-
-    $request = $this->requestStack->getCurrentRequest();
-
-    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.
-      // Check if the active trail has been overridden for this menu tree.
-      $active_path = $this->getPath($menu_name);
-      // 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)) {
-        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];
-            }
-          }
-        }
-      }
-    }
-    return $active_trail;
-  }
-
-  /**
-   * {@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();
-    $menu_name = $tree ? end($tree)['link']['menu_name'] : '';
-
-    // 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($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.
-    $tree = $this->doBuildTree($menu_name, $parameters);
-    // Check access for the current user to each item in the tree.
-    $this->checkAccess($tree);
-    return $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.
-   * MenuTree::checkAccess() 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 && $cache->data) {
-        $this->menuTree[$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());
-      $tree = $this->doBuildTreeData($links, $active_trail, $min_depth);
-
-      // Cache the data, if it is not already in the cache.
-      $this->cache->set($tree_cid, $tree, Cache::PERMANENT, array('menu' => $menu_name));
-      $this->menuTree[$tree_cid] = $tree;
-    }
-
-    return $this->menuTree[$tree_cid];
-  }
-
-  /**
-   * Sorts the menu tree and recursively checks access for each item.
-   *
-   * @param array $tree
-   *   The menu tree you wish to operate on.
-   */
-  protected function checkAccess(&$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->checkAccess($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) {
-    $tree = $this->doBuildTreeData($links, $parents, $depth);
-    $this->checkAccess($tree);
-    return $tree;
-  }
-
-  /**
-   * Prepares the data for calling $this->treeDataRecursive().
-   */
-  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/src/MenuTreeInterface.php b/core/modules/menu_link/src/MenuTreeInterface.php
deleted file mode 100644
index 418f602..0000000
--- a/core/modules/menu_link/src/MenuTreeInterface.php
+++ /dev/null
@@ -1,182 +0,0 @@
-<?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 static::setPath(), the preferred link will be
-   * overridden by the corresponding path returned by static::getPath().
-   *
-   * @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);
-
-  /**
-   * Gets the active trail IDs of the specified menu tree.
-   *
-   * @param string $menu_name
-   *   The menu name of the requested tree.
-   *
-   * @return array
-   *   An array containing the active trail: a list of mlids.
-   */
-  public function getActiveTrailIds($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/src/StaticMenuLinks.php b/core/modules/menu_link/src/StaticMenuLinks.php
deleted file mode 100644
index d42c5ff..0000000
--- a/core/modules/menu_link/src/StaticMenuLinks.php
+++ /dev/null
@@ -1,76 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Menu\StaticMenuLinks.
- */
-
-namespace Drupal\menu_link;
-
-use Drupal\Component\Discovery\YamlDiscovery;
-use Drupal\Core\Extension\ModuleHandlerInterface;
-
-/**
- * Provides a service which finds and alters default menu links in yml files.
- */
-class StaticMenuLinks {
-
-  /**
-   * The module handler.
-   *
-   * @var \Drupal\Core\Extension\ModuleHandlerInterface
-   */
-  protected $moduleHandler;
-
-  /**
-   * Constructs a new StaticMenuLinks.
-   *
-   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
-   *   The module handler.
-   */
-  public function __construct(ModuleHandlerInterface $module_handler) {
-    $this->moduleHandler = $module_handler;
-  }
-
-  /**
-   * Gets the menu links defined in YAML files.
-   *
-   * @return array
-   *   An array of default menu links.
-   */
-  public function getLinks() {
-    $discovery = $this->getDiscovery();
-    foreach ($discovery->findAll() as $module => $menu_links) {
-      foreach ($menu_links as $machine_name => $menu_link) {
-        $all_links[$machine_name] = $menu_link;
-        $all_links[$machine_name]['machine_name'] = $machine_name;
-        $all_links[$machine_name]['module'] = $module;
-      }
-    }
-
-    $this->moduleHandler->alter('menu_link_defaults', $all_links);
-    foreach ($all_links as $machine_name => $menu_link) {
-      // Set the machine_name to the menu links added dynamically.
-      if (!isset($menu_link['machine_name'])) {
-        $all_links[$machine_name]['machine_name'] = $machine_name;
-      }
-      // Change the key to match the DB column for now.
-      $all_links[$machine_name]['link_title'] = $all_links[$machine_name]['title'];
-      unset($all_links[$machine_name]['title']);
-    }
-
-    return $all_links;
-  }
-
-  /**
-   * Creates a YAML discovery for menu links.
-   *
-   * @return \Drupal\Component\Discovery\YamlDiscovery
-   *   An YAML discovery instance.
-   */
-  protected function getDiscovery() {
-    return new YamlDiscovery('links.menu', $this->moduleHandler->getModuleDirectories());
-  }
-
-}
-
diff --git a/core/modules/menu_link/tests/src/MenuTreeTest.php b/core/modules/menu_link/tests/src/MenuTreeTest.php
deleted file mode 100644
index 747335a..0000000
--- a/core/modules/menu_link/tests/src/MenuTreeTest.php
+++ /dev/null
@@ -1,539 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link\Tests\MenuTreeTest.
- */
-
-namespace Drupal\menu_link\Tests {
-
-use Drupal\Core\Cache\Cache;
-use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Language\Language;
-use Drupal\menu_link\MenuTree;
-use Drupal\Tests\UnitTestCase;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\RequestStack;
-
-if (!defined('MENU_MAX_DEPTH')) {
-  define('MENU_MAX_DEPTH', 9);
-}
-
-/**
- * @coversDefaultClass \Drupal\menu_link\MenuTree
- * @group menu_link
- */
-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\State\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}
-   */
-  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\State\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,
-      'weight' => 0,
-      'title' => '',
-      'route_name' => 'example1',
-      'access' => TRUE,
-    );
-    $items[] = array(
-      'mlid' => 2,
-      'depth' => 1,
-      'weight' => 0,
-      'title' => '',
-      'route_name' => 'example2',
-      'access' => TRUE,
-    );
-
-    $result = $this->menuTree->buildTreeData($items, array(), 1);
-
-    $this->assertCount(2, $result);
-    $result1 = array_shift($result);
-    $this->assertEquals($items[0] + array('in_active_trail' => FALSE), $result1['link']);
-    $result2 = array_shift($result);
-    $this->assertEquals($items[1] + array('in_active_trail' => FALSE), $result2['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,
-      'weight' => 0,
-      'title' => '',
-      'route_name' => 'example1',
-      'access' => TRUE,
-    );
-    $items[] = array(
-      'mlid' => 2,
-      'depth' => 1,
-      'weight' => 0,
-      'title' => '',
-      'route_name' => 'example2',
-      'access' => TRUE,
-    );
-
-    $result = $this->menuTree->buildTreeData($items, array(1), 1);
-
-    $this->assertCount(2, $result);
-    $result1 = array_shift($result);
-    $this->assertEquals($items[0] + array('in_active_trail' => TRUE), $result1['link']);
-    $result2 = array_shift($result);
-    $this->assertEquals($items[1] + array('in_active_trail' => FALSE), $result2['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,
-      'weight' => 0,
-      'title' => '',
-      'route_name' => 'example1',
-      'access' => TRUE,
-    );
-    $items[] = array(
-      'mlid' => 2,
-      'depth' => 1,
-      'weight' => 0,
-      'title' => '',
-      'route_name' => 'example2',
-      'access' => TRUE,
-    );
-
-    $result = $this->menuTree->buildTreeData($items, array(3), 1);
-
-    $this->assertCount(2, $result);
-    $result1 = array_shift($result);
-    $this->assertEquals($items[0] + array('in_active_trail' => FALSE), $result1['link']);
-    $result2 = array_shift($result);
-    $this->assertEquals($items[1] + array('in_active_trail' => FALSE), $result2['link']);
-  }
-
-  /**
-   * Tests buildTreeData with a more complex example.
-   *
-   * @covers ::buildTreeData
-   * @covers ::doBuildTreeData
-   */
-  public function testBuildTreeWithComplexData() {
-    $items = array(
-      1 => array('mlid' => 1, 'depth' => 1, 'route_name' => 'example1', 'access' => TRUE, 'weight' => 0, 'title' => ''),
-      2 => array('mlid' => 2, 'depth' => 1, 'route_name' => 'example2', 'access' => TRUE, 'weight' => 0, 'title' => ''),
-      3 => array('mlid' => 3, 'depth' => 2, 'route_name' => 'example3', 'access' => TRUE, 'weight' => 0, 'title' => ''),
-      4 => array('mlid' => 4, 'depth' => 3, 'route_name' => 'example4', 'access' => TRUE, 'weight' => 0, 'title' => ''),
-      5 => array('mlid' => 5, 'depth' => 1, 'route_name' => 'example5', 'access' => TRUE, 'weight' => 0, 'title' => ''),
-    );
-
-    $tree = $this->menuTree->buildTreeData($items);
-
-    // Validate that parent items #1, #2, and #5 exist on the root level.
-    $this->assertEquals($items[1]['mlid'], $tree['50000  1']['link']['mlid']);
-    $this->assertEquals($items[2]['mlid'], $tree['50000  2']['link']['mlid']);
-    $this->assertEquals($items[5]['mlid'], $tree['50000  5']['link']['mlid']);
-
-    // Validate that child item #4 exists at the correct location in the hierarchy.
-    $this->assertEquals($items[4]['mlid'], $tree['50000  2']['below']['50000  3']['below']['50000  4']['link']['mlid']);
-  }
-
-  /**
-   * Tests getActiveTrailIds().
-   *
-   * @covers ::getActiveTrailIds()
-   */
-  public function testGetActiveTrailIds() {
-    $menu_link = array(
-      'mlid' => 10,
-      'route_name' => 'example1',
-      'p1' => 3,
-      'p2' => 2,
-      'p3' => 1,
-      'p4' => 4,
-      'p5' => 9,
-      'p6' => 5,
-      'p7' => 6,
-      'p8' => 7,
-      'p9' => 8,
-      'menu_name' => 'test_menu'
-    );
-    $this->menuTree->setPreferredMenuLink('test_menu', 'test/path', $menu_link);
-    $request = (new Request());
-    $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'test_route');
-    $this->requestStack->push($request);
-    $this->menuTree->setPath('test_menu', 'test/path');
-
-    $trail = $this->menuTree->getActiveTrailIds('test_menu');
-    $this->assertEquals(array(0 => 0, 3 => 3, 2 => 2, 1 => 1, 4 => 4, 9 => 9, 5 => 5, 6 => 6, 7 => 7), $trail);
-  }
-
-  /**
-   * Tests getActiveTrailIds() without preferred link.
-   *
-   * @covers ::getActiveTrailIds()
-   */
-  public function testGetActiveTrailIdsWithoutPreferredLink() {
-    $request = (new Request());
-    $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'test_route');
-    $this->requestStack->push($request);
-    $this->menuTree->setPath('test_menu', 'test/path');
-
-    $trail = $this->menuTree->getActiveTrailIds('test_menu');
-    $this->assertEquals(array(0 => 0), $trail);
-  }
-
-
-  /**
-   * Tests buildTree with simple menu_name and no parameters.
-   */
-  public function testBuildTreeWithoutParameters() {
-    $language = new Language(array('id' => 'en'));
-    $this->languageManager->expects($this->any())
-      ->method('getCurrentLanguage')
-      ->will($this->returnValue($language));
-
-    // Setup query and the query result.
-    $query = $this->getMock('Drupal\Core\Entity\Query\QueryInterface');
-    $this->entityQueryFactory->expects($this->once())
-      ->method('get')
-      ->with('menu_link')
-      ->will($this->returnValue($query));
-    $query->expects($this->once())
-      ->method('condition')
-      ->with('menu_name', 'test_menu');
-    $query->expects($this->once())
-      ->method('execute')
-      ->will($this->returnValue(array(1, 2, 3)));
-
-    $storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
-    $base = array(
-      'access' => TRUE,
-      'weight' => 0,
-      'title' => 'title',
-    );
-    $menu_link = $base + array(
-      'mlid' => 1,
-      'p1' => 3,
-      'p2' => 2,
-      'p3' => 1,
-    );
-    $links[1] = $menu_link;
-    $menu_link = $base + array(
-      'mlid' => 3,
-      'p1' => 3,
-      'depth' => 1,
-    );
-    $links[3] = $menu_link;
-    $menu_link = $base + array(
-      'mlid' => 2,
-      'p1' => 3,
-      'p2' => 2,
-      'depth' => 2,
-    );
-    $links[2] = $menu_link;
-    $storage->expects($this->once())
-      ->method('loadMultiple')
-      ->with(array(1, 2, 3))
-      ->will($this->returnValue($links));
-    $this->menuTree->setStorage($storage);
-
-    // Ensure that static/non static caching works.
-    // First setup no working caching.
-    $this->cacheBackend->expects($this->at(0))
-      ->method('get')
-      ->with('links:test_menu:tree-data:en:35786c7117b4e38d0f169239752ce71158266ae2f6e4aa230fbbb87bd699c0e3')
-      ->will($this->returnValue(FALSE));
-    $this->cacheBackend->expects($this->at(1))
-      ->method('set')
-      ->with('links:test_menu:tree-data:en:35786c7117b4e38d0f169239752ce71158266ae2f6e4aa230fbbb87bd699c0e3', $this->anything(), Cache::PERMANENT, array('menu' => 'test_menu'));
-
-    // Ensure that the static caching triggered.
-    $this->cacheBackend->expects($this->exactly(1))
-      ->method('get');
-
-    $this->menuTree->buildTree('test_menu');
-    $this->menuTree->buildTree('test_menu');
-  }
-
-  /**
-   * Tests the output with a single level.
-   *
-   * @covers ::renderTree
-   */
-  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 ::renderTree
-   */
-  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->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;
-
-  /**
-   * Stores the preferred menu link per menu and path.
-   *
-   * @var array
-   */
-  protected $preferredMenuLink;
-
-  /**
-   * {@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) {
-    return isset($this->preferredMenuLink[$menu_name][$active_path]) ? $this->preferredMenuLink[$menu_name][$active_path] : NULL;
-  }
-
-  /**
-   * Set the storage.
-   *
-   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
-   *   The menu link storage.
-   */
-  public function setStorage(EntityStorageInterface $storage) {
-    $this->menuLinkStorage = $storage;
-  }
-
-  /**
-   * Sets the preferred menu link.
-   *
-   * @param string $menu_name
-   *   The menu name.
-   * @param string $active_path
-   *   The active path.
-   * @param array $menu_link
-   *   The preferred menu link.
-   */
-  public function setPreferredMenuLink($menu_name, $active_path, $menu_link) {
-    $this->preferredMenuLink[$menu_name][$active_path] = $menu_link;
-  }
-
-}
-
-}
-
-namespace {
-  if (!defined('MENU_MAX_DEPTH')) {
-    define('MENU_MAX_DEPTH', 9);
-  }
-}
diff --git a/core/modules/menu_link_content/menu_link_content.install b/core/modules/menu_link_content/menu_link_content.install
new file mode 100644
index 0000000..31fa93f
--- /dev/null
+++ b/core/modules/menu_link_content/menu_link_content.install
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the menu_link_content module.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function menu_link_content_uninstall() {
+  // Find all the entities and then call the manager and delete all the plugins.
+  $query = \Drupal::entityQueryAggregate('menu_link_content');
+  $query->groupBy('uuid');
+  $result = $query->execute();
+  $uuids = array();
+  foreach ($result as $row) {
+    $uuids[] = $row['uuid'];
+  }
+
+  /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
+  $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
+  foreach ($uuids as $uuid) {
+    // Manually build the plugin ID, and remove it from the menu tree.
+    $menu_link_manager->removeDefinition("menu_link_content:$uuid", FALSE);
+  }
+}
diff --git a/core/modules/menu_link_content/menu_link_content.module b/core/modules/menu_link_content/menu_link_content.module
index 25dfb26..6d33dce 100644
--- a/core/modules/menu_link_content/menu_link_content.module
+++ b/core/modules/menu_link_content/menu_link_content.module
@@ -5,9 +5,30 @@
  * Allows administrators to create custom menu links.
  */
 
+use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\system\MenuInterface;
 
 /**
+ * Implements hook_help().
+ */
+function menu_link_content_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    case 'help.page.menu_link_content':
+      $output = '';
+      $output .= '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('The Custom Menu Links module allows users to create menu links. These links can be translated if multiple languages are used for the site.');
+      if (\Drupal::moduleHandler()->moduleExists('menu_ui')) {
+        $output .= ' ' . t('It is required by the Menu UI module, which provides an interface for managing menus and menu links. See the <a href="!menu-help">Menu UI module help page</a> for more information.', array('!menu-help' => \Drupal::url('help.page', array('name' => 'menu_ui'))));
+      }
+      else {
+        $output .= ' ' . t('If you enable the Menu UI module, it provides an interface for managing menus and menu links.');
+      }
+      $output .= '</p>';
+      return $output;
+  }
+}
+
+/**
  * Implements hook_menu_delete().
  */
 function menu_link_content_menu_delete(MenuInterface $menu) {
diff --git a/core/modules/menu_link_content/menu_link_content.routing.yml b/core/modules/menu_link_content/menu_link_content.routing.yml
index f96b22d..cebc134 100644
--- a/core/modules/menu_link_content/menu_link_content.routing.yml
+++ b/core/modules/menu_link_content/menu_link_content.routing.yml
@@ -11,6 +11,8 @@ menu_link_content.link_edit:
   defaults:
     _entity_form: 'menu_link_content.default'
     _title: 'Edit menu link'
+  options:
+    _admin_route: TRUE
   requirements:
     _entity_access: 'menu_link_content.update'
 
diff --git a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
index efbc76c..54f174a 100644
--- a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
+++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
@@ -144,13 +144,6 @@ public function setMenuLinkInstance(MenuLinkInterface $menu_link) {
    * {@inheritdoc}
    */
   public function buildConfigurationForm(array $form, array &$form_state) {
-    return $this->buildEditForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildEditForm(array &$form, array &$form_state) {
     $this->setOperation('default');
     $this->init($form_state);
 
@@ -167,26 +160,10 @@ public function validateConfigurationForm(array &$form, array &$form_state) {
   /**
    * {@inheritdoc}
    */
-  public function validateEditForm(array &$form, array &$form_state) {
-    $this->doValidate($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function submitConfigurationForm(array &$form, array &$form_state) {
-    return $this->submitEditForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitEditForm(array &$form, array &$form_state) {
     // Remove button and internal Form API values from submitted values.
-    form_state_values_clean($form_state);
-    $this->entity = $this->buildEntity($form, $form_state);
-    $this->entity->save();
-    return $form_state;
+    parent::submit($form, $form_state);
+    $this->save($form, $form_state);
   }
 
   /**
diff --git a/core/modules/menu_ui/menu_ui.admin.inc b/core/modules/menu_ui/menu_ui.admin.inc
index 0d7a0fd..f23093f 100644
--- a/core/modules/menu_ui/menu_ui.admin.inc
+++ b/core/modules/menu_ui/menu_ui.admin.inc
@@ -5,15 +5,19 @@
  * Administrative page callbacks for Menu UI module.
  */
 
+use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Render\Element;
 
 /**
  * Returns HTML for the menu overview form into a table.
  *
- * @param $variables
+ * @param array $variables
  *   An associative array containing:
  *   - form: A render element representing the form.
  *
+ * @return string
+ *   The themed HTML.
+ *
  * @ingroup themeable
  */
 function theme_menu_overview_form($variables) {
@@ -27,27 +31,28 @@ function theme_menu_overview_form($variables) {
   );
 
   $rows = array();
-  foreach (Element::children($form) as $mlid) {
-    if (isset($form[$mlid]['hidden'])) {
-      $element = &$form[$mlid];
+  foreach (Element::children($form) as $id) {
+    if (isset($form[$id]['#item'])) {
+      $element = &$form[$id];
 
       // Add special classes to be used for tabledrag.js.
-      $element['plid']['#attributes']['class'] = array('menu-plid');
-      $element['mlid']['#attributes']['class'] = array('menu-mlid');
+      $element['parent']['#attributes']['class'] = array('menu-parent');
+      $element['id']['#attributes']['class'] = array('menu-id');
       $element['weight']['#attributes']['class'] = array('menu-weight');
 
-      // Change the parent field to a hidden. This allows any value but hides the field.
-      $element['plid']['#type'] = 'hidden';
+      // Change the parent field to a hidden. This allows any value but hides
+      // the field.
+      $element['parent']['#type'] = 'hidden';
 
       $indent = array(
         '#theme' => 'indentation',
-        '#size' => $element['#item']['depth'] - 1,
+        '#size' => $element['#item']->depth - 1,
       );
 
       $row = array();
-      $row[] = drupal_render($indent) . drupal_render($element['title']);
-      $row[] = array('data' => drupal_render($element['hidden']), 'class' => array('checkbox', 'menu-enabled'));
-      $row[] = drupal_render($element['weight']) . drupal_render($element['plid']) . drupal_render($element['mlid']);
+      $row[] = SafeMarkup::set(drupal_render($indent) . drupal_render($element['title']));
+      $row[] = array('data' => drupal_render($element['enabled']), 'class' => array('checkbox', 'menu-enabled'));
+      $row[] = SafeMarkup::set(drupal_render($element['weight']) . drupal_render($element['parent']) . drupal_render($element['id']));
       $row[] = drupal_render($element['operations']);
 
       $row = array_merge(array('data' => $row), $element['#attributes']);
@@ -71,11 +76,11 @@ function theme_menu_overview_form($variables) {
       array(
         'action' => 'match',
         'relationship' => 'parent',
-        'group' => 'menu-plid',
-        'subgroup' => 'menu-plid',
-        'source' => 'menu-mlid',
+        'group' => 'menu-parent',
+        'subgroup' => 'menu-parent',
+        'source' => 'menu-id',
         'hidden' => TRUE,
-        'limit' => MENU_MAX_DEPTH - 1,
+        'limit' => \Drupal::menuTree()->maxDepth() - 1,
       ),
       array(
         'action' => 'order',
diff --git a/core/modules/menu_ui/menu_ui.info.yml b/core/modules/menu_ui/menu_ui.info.yml
index 26611f1..f3bf1be 100644
--- a/core/modules/menu_ui/menu_ui.info.yml
+++ b/core/modules/menu_ui/menu_ui.info.yml
@@ -6,4 +6,4 @@ version: VERSION
 core: 8.x
 configure: menu_ui.overview_page
 dependencies:
-  - menu_link
+  - menu_link_content
diff --git a/core/modules/menu_ui/menu_ui.links.action.yml b/core/modules/menu_ui/menu_ui.links.action.yml
index 0834821..af8716f 100644
--- a/core/modules/menu_ui/menu_ui.links.action.yml
+++ b/core/modules/menu_ui/menu_ui.links.action.yml
@@ -1,6 +1,7 @@
-menu_ui_link_add:
-  route_name: menu_ui.link_add
+menu_ui.link_add:
+  route_name: menu_link_content.link_add
   title: 'Add link'
+  class: \Drupal\menu_ui\Plugin\Menu\LocalAction\MenuLinkAdd
   appears_on:
     - menu_ui.menu_edit
 
diff --git a/core/modules/menu_ui/menu_ui.module b/core/modules/menu_ui/menu_ui.module
index 70b5b59..79cd051 100644
--- a/core/modules/menu_ui/menu_ui.module
+++ b/core/modules/menu_ui/menu_ui.module
@@ -10,13 +10,13 @@
 
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\block\BlockPluginInterface;
+use Drupal\Core\Menu\MenuLinkInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\menu_link_content\Entity\MenuLinkContent;
 use Drupal\node\NodeTypeInterface;
 use Drupal\system\Entity\Menu;
 use Symfony\Component\HttpFoundation\JsonResponse;
-use Drupal\menu_link\Entity\MenuLink;
-use Drupal\menu_link\MenuLinkStorage;
 use Drupal\node\NodeInterface;
 
 /**
@@ -72,31 +72,11 @@ function menu_ui_entity_type_build(array &$entity_types) {
     ->setFormClass('edit', 'Drupal\menu_ui\MenuForm')
     ->setFormClass('delete', 'Drupal\menu_ui\Form\MenuDeleteForm')
     ->setListBuilderClass('Drupal\menu_ui\MenuListBuilder')
-    ->setLinkTemplate('add-form', 'menu_ui.link_add')
+    ->setLinkTemplate('add-form', 'menu_ui.menu_add')
     ->setLinkTemplate('delete-form', 'menu_ui.delete_menu')
     ->setLinkTemplate('edit-form', 'menu_ui.menu_edit');
-
-  $entity_types['menu_link']
-    ->setFormClass('delete', 'Drupal\menu_ui\Form\MenuLinkDeleteForm')
-    ->setFormClass('reset', 'Drupal\menu_ui\Form\MenuLinkResetForm')
-    ->setLinkTemplate('delete-form', 'menu_ui.link_delete');
 }
 
-/**
- * Implements hook_entity_bundle_info().
- */
-function menu_ui_entity_bundle_info() {
-  $bundles = array();
-  $config_names = \Drupal::configFactory()->listAll('system.menu.');
-  foreach ($config_names as $config_name) {
-    $config = \Drupal::config($config_name);
-    $bundles['menu_link'][$config->get('id')] = array(
-      'label' => $config->get('label'),
-    );
-  }
-
-  return $bundles;
-}
 
 /**
  * Implements hook_theme().
@@ -119,23 +99,6 @@ function menu_ui_menu_insert(Menu $menu) {
   if (\Drupal::moduleHandler()->moduleExists('block')) {
     \Drupal::service('plugin.manager.block')->clearCachedDefinitions();
   }
-
-  if ($menu->isSyncing()) {
-    return;
-  }
-
-  // Make sure the menu is present in the active menus variable so that its
-  // items may appear in the menu active trail.
-  // See menu_set_active_menu_names().
-  $config = \Drupal::config('system.menu');
-
-  $active_menus = $config->get('active_menus_default') ?: array_keys(menu_ui_get_menus());
-  if (!in_array($menu->id(), $active_menus)) {
-    $active_menus[] = $menu->id();
-    $config
-      ->set('active_menus_default', $active_menus)
-      ->save();
-  }
 }
 
 /**
@@ -154,20 +117,9 @@ function menu_ui_menu_update(Menu $menu) {
  */
 function menu_ui_menu_predelete(Menu $menu) {
   // Delete all links from the menu.
-  menu_delete_links($menu->id());
-
-  // Remove menu from active menus variable.
-  $config = \Drupal::config('system.menu');
-  $active_menus = $config->get('active_menus_default') ?: array_keys(menu_ui_get_menus());
-  if (in_array($menu->id(), $active_menus)) {
-    $active_menus = array_diff($active_menus, array($menu->id()));
-    // Prevent the gap left by the removed menu from causing array indices to
-    // be saved.
-    $active_menus = array_values($active_menus);
-    $config
-      ->set('active_menus_default', $active_menus)
-      ->save();
-  }
+  /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
+  $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
+  $menu_link_manager->deleteLinksInMenu($menu->id());
 }
 
 /**
@@ -183,110 +135,13 @@ function menu_ui_menu_delete(Menu $menu) {
 }
 
 /**
- * Returns a list of menu links that are valid possible parents for the given
- * menu link.
- *
- * @param array $menus
- *   An array of menu names and titles, such as from menu_ui_get_menus().
- * @param \Drupal\menu_link\Entity\MenuLink $menu_link
- *   The menu link for which to generate a list of parents.
- *   If $menu_link->id() == 0 then the complete tree is returned.
- * @param string $type
- *   The node type for which to generate a list of parents.
- *   If $item itself is a node type then $type is ignored.
- *
- * @return array
- *   An array of menu link titles keyed by a string containing the menu name and
- *   mlid. The list excludes the given item and its children.
- *
- * @todo This has to be turned into a #process form element callback. The
- *   'override_parent_selector' variable is entirely superfluous.
- */
-function menu_ui_parent_options(array $menus, MenuLink $menu_link = NULL, $type = NULL) {
-  // The menu_links table can be practically any size and we need a way to
-  // allow contrib modules to provide more scalable pattern choosers.
-  // hook_form_alter is too late in itself because all the possible parents are
-  // retrieved here, unless override_parent_selector is set to TRUE.
-  if (\Drupal::config('menu_ui.settings')->get('override_parent_selector')) {
-    return array();
-  }
-
-  if (!$menu_link) {
-    $menu_link = entity_create('menu_link', array('mlid' => 0));
-  }
-
-  $available_menus = array();
-  if (!$type) {
-    // If no node type is set, use all menus given to this function.
-    $available_menus = $menus;
-  }
-  else {
-    // If a node type is set, use all available menus for this type.
-    $type_menus = \Drupal::config("menu.entity.node.$type")->get('available_menus');
-    foreach ($type_menus as $menu) {
-      $available_menus[$menu] = $menu;
-    }
-  }
-
-  return _menu_ui_get_options($menus, $available_menus, $menu_link);
-}
-
-/**
- * Helper function to get the items of the given menu.
- */
-function _menu_ui_get_options($menus, $available_menus, $item) {
-  // If the item has children, there is an added limit to the depth of valid parents.
-  if (isset($item['parent_depth_limit'])) {
-    $limit = $item['parent_depth_limit'];
-  }
-  else {
-    $limit = _menu_ui_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->buildAllData($menu_name, NULL);
-      $options[$menu_name . ':0'] = '<' . $title . '>';
-      _menu_ui_parents_recurse($tree, $menu_name, '--', $options, $item['mlid'], $limit);
-    }
-  }
-  return $options;
-}
-
-/**
- * Recursive helper function for menu_ui_parent_options().
- */
-function _menu_ui_parents_recurse($tree, $menu_name, $indent, &$options, $exclude, $depth_limit) {
-  foreach ($tree as $data) {
-    if ($data['link']['depth'] > $depth_limit) {
-      // Don't iterate through any links on this level.
-      break;
-    }
-    if ($data['link']['mlid'] != $exclude && $data['link']['hidden'] >= 0) {
-      $title = $indent . ' ' . truncate_utf8($data['link']['title'], 30, TRUE, FALSE);
-      if ($data['link']['hidden']) {
-        $title .= ' (' . t('disabled') . ')';
-      }
-      $options[$menu_name . ':' . $data['link']['mlid']] = $title;
-      if ($data['below']) {
-        _menu_ui_parents_recurse($data['below'], $menu_name, $indent . '--', $options, $exclude, $depth_limit);
-      }
-    }
-  }
-}
-
-/**
  * Implements hook_block_view_BASE_BLOCK_ID_alter() for 'system_menu_block'.
  */
 function menu_ui_block_view_system_menu_block_alter(array &$build, BlockPluginInterface $block) {
   // Add contextual links for system menu blocks.
   $menus = menu_list_system_menus();
   $menu_name = $block->getDerivativeId();
-  if (isset($menus[$menu_name])) {
+  if (isset($menus[$menu_name]) && isset($build['content'])) {
     $build['#contextual_links']['menu'] = array(
       'route_parameters' => array('menu' => $menu_name),
     );
@@ -316,7 +171,7 @@ function menu_ui_node_type_insert(NodeTypeInterface $type) {
   }
   \Drupal::config('menu.entity.node.' . $type->id())
     ->set('available_menus', array('main'))
-    ->set('parent', 'main:0')
+    ->set('parent', 'main:')
     ->save();
 }
 
@@ -334,25 +189,35 @@ function menu_ui_node_type_delete(NodeTypeInterface $type) {
  * Helper for hook_ENTITY_TYPE_insert() and hook_ENTITY_TYPE_update() for nodes.
  */
 function menu_ui_node_save(EntityInterface $node) {
-  if (isset($node->menu)) {
-    $link = &$node->menu;
-    if (empty($link['enabled'])) {
-      if (!$link->isNew()) {
-        menu_link_delete($link['mlid']);
-      }
-    }
-    elseif (trim($link['link_title'])) {
-      $link['link_title'] = trim($link['link_title']);
-      $link['link_path'] = 'node/' . $node->id();
-      if (trim($link['description'])) {
-        $link['options']['attributes']['title'] = trim($link['description']);
+  if (!empty($node->menu)) {
+    /** @var \Drupal\menu_link_content\Entity\MenuLinkContentInterface $entity */
+    $definition = $node->menu;
+    if (trim($definition['title'])) {
+      if (!empty($definition['entity_id'])) {
+        $entity = entity_load('menu_link_content', $definition['entity_id']);
+        $entity->hidden->value = 0;
+        $entity->title->value = trim($definition['title']);
+        $entity->description->value = trim($definition['description']);
+        $entity->menu_name->value = $definition['menu_name'];
+        $entity->parent->value = $definition['parent'];
+        $entity->weight->value = isset($definition['weight']) ? $definition['weight'] : 0;
       }
       else {
-        // If the description field was left empty, remove the title attribute
-        // from the menu link.
-        unset($link['options']['attributes']['title']);
+        // Create a new menu_link_content entity.
+        $entity = entity_create('menu_link_content', array(
+          'title' => trim($definition['title']),
+          'description' => trim($definition['description']),
+          'route_name' => 'node.view',
+          'route_parameters' => array('node' => $node->id()),
+          'menu_name' => $definition['menu_name'],
+          'parent' => $definition['parent'],
+          'weight' => isset($definition['weight']) ? $definition['weight'] : 0,
+          'hidden' => 0,
+          'bundle' => 'menu_link_content',
+          'langcode' => $node->getUntranslated()->language()->id,
+        ));
       }
-      if (!menu_link_save($link)) {
+      if (!$entity->save()) {
         drupal_set_message(t('There was an error saving the menu link.'), 'error');
       }
     }
@@ -363,14 +228,17 @@ function menu_ui_node_save(EntityInterface $node) {
  * Implements hook_ENTITY_TYPE_predelete() for node entities.
  */
 function menu_ui_node_predelete(EntityInterface $node) {
-  // Delete all Menu UI module links that point to this node.
-  $query = \Drupal::entityQuery('menu_link')
-    ->condition('link_path', 'node/' . $node->id())
-    ->condition('module', 'menu');
-  $result = $query->execute();
+  // Delete all MenuLinkContent links that point to this node.
+  /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
+  $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
+  $result = $menu_link_manager->loadLinksByRoute('node.view', array('node' => $node->id()));
 
   if (!empty($result)) {
-    menu_link_delete_multiple($result);
+    foreach ($result as $id => $instance) {
+      if ($instance->isDeletable() && strpos($id, 'menu_link_content:') === 0) {
+        $instance->deleteLink();
+      }
+    }
   }
 }
 
@@ -378,67 +246,69 @@ function menu_ui_node_predelete(EntityInterface $node) {
  * Implements hook_node_prepare_form().
  */
 function menu_ui_node_prepare_form(NodeInterface $node, $operation, array &$form_state) {
-  if (empty($node->menu)) {
+  if (empty($form_state['menu_link_definition'])) {
     // Prepare the node for the edit form so that $node->menu always exists.
     $node_type_config = \Drupal::config('menu.entity.node.' . $node->getType());
     $menu_name = strtok($node_type_config->get('parent'), ':');
-    $menu_link = FALSE;
+    $definition = FALSE;
     if ($node->id()) {
-      $mlid = FALSE;
+      $id = FALSE;
       // Give priority to the default menu
       $type_menus = $node_type_config->get('available_menus');
       if (in_array($menu_name, $type_menus)) {
-        $query = \Drupal::entityQuery('menu_link')
-          ->condition('link_path', 'node/' . $node->id())
+        $query = \Drupal::entityQuery('menu_link_content')
+          ->condition('route_name', 'node.view')
+          ->condition('route_parameters', serialize(array('node' => $node->id())))
           ->condition('menu_name', $menu_name)
-          ->condition('module', 'menu_ui')
-          ->sort('mlid', 'ASC')
+          ->sort('id', 'ASC')
           ->range(0, 1);
         $result = $query->execute();
 
-        $mlid = (!empty($result)) ? reset($result) : FALSE;
+        $id = (!empty($result)) ? reset($result) : FALSE;
       }
       // Check all allowed menus if a link does not exist in the default menu.
-      if (!$mlid && !empty($type_menus)) {
-        $query = \Drupal::entityQuery('menu_link')
-          ->condition('link_path', 'node/' . $node->id())
+      if (!$id && !empty($type_menus)) {
+        $query = \Drupal::entityQuery('menu_link_content')
+          ->condition('route_name', 'node.view')
+          ->condition('route_parameters', serialize(array('node' => $node->id())))
           ->condition('menu_name', array_values($type_menus), 'IN')
-          ->condition('module', 'menu_ui')
-          ->sort('mlid', 'ASC')
+          ->sort('id', 'ASC')
           ->range(0, 1);
         $result = $query->execute();
 
-        $mlid = (!empty($result)) ? reset($result) : FALSE;
+        $id = (!empty($result)) ? reset($result) : FALSE;
       }
-      if ($mlid) {
-        $menu_link = menu_link_load($mlid);
+      if ($id) {
+        $menu_link = MenuLinkContent::load($id);
+        $definition = array(
+          'entity_id' => $menu_link->id(),
+          'id' => $menu_link->getPluginId(),
+          'title' => $menu_link->getTitle(),
+          'description' => $menu_link->getDescription(),
+          'menu_name' => $menu_link->getMenuName(),
+          'parent' => $menu_link->getParentId(),
+          'weight' => $menu_link->getWeight(),
+        );
       }
     }
 
-    if (!$menu_link) {
-      $menu_link = entity_create('menu_link', array(
-        'mlid' => 0,
-        'plid' => 0,
+    if (!$definition) {
+      $definition = array(
+        'entity_id' => 0,
+        'id' => '',
+        'title' => '',
+        'description' => '',
         'menu_name' => $menu_name,
-      ));
+        'parent' => '',
+        'weight' => 0,
+      );
     }
     // Set default values.
-    $node->menu = $menu_link;
-  }
-  // Find the depth limit for the parent select.
-  if (!isset($node->menu['parent_depth_limit'])) {
-    $node->menu['parent_depth_limit'] = _menu_ui_parent_depth_limit($node->menu);
+    $form_state['menu_link_definition'] = $definition;
   }
 }
 
 /**
- * Find the depth limit for items in the parent select.
- */
-function _menu_ui_parent_depth_limit($item) {
-  return MENU_MAX_DEPTH - 1 - (($item['mlid'] && $item['has_children']) ? entity_get_controller('menu_link')->findChildrenRelativeDepth($item) : 0);
-}
-
-/**
  * Implements hook_form_BASE_FORM_ID_alter() for node_form.
  *
  * Adds menu item fields to the node form.
@@ -449,11 +319,25 @@ function menu_ui_form_node_form_alter(&$form, $form_state) {
   // Generate a list of possible parents (not including this link or descendants).
   // @todo This must be handled in a #process handler.
   $node = $form_state['controller']->getEntity();
-  $link = $node->menu;
+  $definition = $form_state['menu_link_definition'];
   $type = $node->getType();
-  $options = menu_ui_parent_options(menu_ui_get_menus(), $link, $type);
+  /** @var \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_selector */
+  $menu_parent_selector = \Drupal::service('menu.parent_form_selector');
+  $menu_names = menu_ui_get_menus();
+  $type_menus = \Drupal::config("menu.entity.node.$type")->get('available_menus');
+  $available_menus = array();
+  foreach ($type_menus as $menu) {
+    $available_menus[$menu] = $menu_names[$menu];
+  }
+  if ($definition['id']) {
+    $default = $definition['menu_name'] . ':' . $definition['parent'];
+  }
+  else {
+    $default = \Drupal::config('menu.entity.node.'.$type)->get('parent');
+  }
+  $parent_element = $menu_parent_selector->parentSelectElement($default,  $definition['id'], $available_menus);
   // If no possible parent menu items were found, there is nothing to display.
-  if (empty($options)) {
+  if (empty($parent_element)) {
     return;
   }
 
@@ -461,7 +345,7 @@ function menu_ui_form_node_form_alter(&$form, $form_state) {
     '#type' => 'details',
     '#title' => t('Menu settings'),
     '#access' => \Drupal::currentUser()->hasPermission('administer menu'),
-    '#open' => !empty($link['link_title']),
+    '#open' => (bool) $definition['id'],
     '#group' => 'advanced',
     '#attached' => array(
       'library' => array('menu/drupal.menu'),
@@ -473,7 +357,7 @@ function menu_ui_form_node_form_alter(&$form, $form_state) {
   $form['menu']['enabled'] = array(
     '#type' => 'checkbox',
     '#title' => t('Provide a menu link'),
-    '#default_value' => (int) (bool) $link['mlid'],
+    '#default_value' => (int) (bool) $definition['id'],
   );
   $form['menu']['link'] = array(
     '#type' => 'container',
@@ -486,57 +370,32 @@ function menu_ui_form_node_form_alter(&$form, $form_state) {
   );
 
   // Populate the element with the link data.
-  foreach (array('mlid', 'module', 'hidden', 'has_children', 'customized', 'options', 'expanded', 'hidden', 'parent_depth_limit') as $key) {
-    $form['menu']['link'][$key] = array('#type' => 'value', '#value' => $link[$key]);
+  foreach (array('id', 'entity_id') as $key) {
+    $form['menu']['link'][$key] = array('#type' => 'value', '#value' => $definition[$key]);
   }
 
-  $form['menu']['link']['link_title'] = array(
+  $form['menu']['link']['title'] = array(
     '#type' => 'textfield',
     '#title' => t('Menu link title'),
-    '#default_value' => $link['link_title'],
+    '#default_value' => $definition['title'],
   );
 
   $form['menu']['link']['description'] = array(
     '#type' => 'textarea',
     '#title' => t('Description'),
-    '#default_value' => isset($link['options']['attributes']['title']) ? $link['options']['attributes']['title'] : '',
+    '#default_value' => $definition['description'],
     '#rows' => 1,
     '#description' => t('Shown when hovering over the menu link.'),
   );
 
-  if ($link['mlid']) {
-    $default = $link['menu_name'] . ':' . $link['plid'];
-  }
-  else {
-    $default = \Drupal::config('menu.entity.node.'.$type)->get('parent');
-  }
-  // If the current parent menu item is not present in options, use the first
-  // available option as default value.
-  // @todo User should not be allowed to access menu link settings in such a
-  // case.
-  if (!isset($options[$default])) {
-    $array = array_keys($options);
-    $default = reset($array);
-  }
-  $form['menu']['link']['parent'] = array(
-    '#type' => 'select',
-    '#title' => t('Parent item'),
-    '#default_value' => $default,
-    '#options' => $options,
-    '#attributes' => array('class' => array('menu-parent-select')),
-  );
+  $form['menu']['link']['menu_parent'] = $parent_element;
+  $form['menu']['link']['menu_parent']['#title'] = t('Parent item');
+  $form['menu']['link']['menu_parent']['#attributes']['class'][] = 'menu-parent-select';
 
-  // Get number of items in menu so the weight selector is sized appropriately.
-  $delta = entity_get_controller('menu_link')->countMenuLinks($link->menu_name);
-  if ($delta < 50) {
-    // Old hardcoded value
-    $delta = 50;
-  }
   $form['menu']['link']['weight'] = array(
-    '#type' => 'weight',
+    '#type' => 'number',
     '#title' => t('Weight'),
-    '#delta' => $delta,
-    '#default_value' => $link['weight'],
+    '#default_value' => $definition['weight'],
     '#description' => t('Menu links with lower weights are displayed before links with higher weights.'),
   );
 }
@@ -548,18 +407,26 @@ function menu_ui_form_node_form_alter(&$form, $form_state) {
  */
 function menu_ui_node_submit(EntityInterface $node, $form, $form_state) {
   if (!empty($form_state['values']['menu'])) {
-    $original_menu_id = !empty($node->menu) ? $node->menu->id() : NULL;
-    $node->menu = entity_create('menu_link', $form_state['values']['menu']);
-    // @todo Do not create a new entity in order to update it, see
-    //   https://drupal.org/node/2241865
-    // If this menu had a previous menu link associated, mark it as not new.
-    if ($original_menu_id) {
-      $node->menu->setOriginalId($original_menu_id);
+    $definition = $form_state['values']['menu'];
+    if (empty($definition['enabled'])) {
+      if ($definition['entity_id']) {
+        $entity = entity_load('menu_link_content', $definition['entity_id']);
+        $entity->delete();
+      }
     }
-    // Decompose the selected menu parent option into 'menu_name' and 'plid', if
-    // the form used the default parent selection widget.
-    if (!empty($form_state['values']['menu']['parent'])) {
-      list($node->menu['menu_name'], $node->menu['plid']) = explode(':', $form_state['values']['menu']['parent']);
+    elseif (trim($definition['title']))  {
+      // Decompose the selected menu parent option into 'menu_name' and 'parent',
+      // if the form used the default parent selection widget.
+      if (!empty($definition['menu_parent'])) {
+        list($menu_name, $parent) = explode(':', $definition['menu_parent'], 2);
+        $definition['menu_name'] = $menu_name;
+        $definition['parent'] = $parent;
+      }
+      // @todo Figure out how to save this data without adding non-Field API
+      //   properties to the node entity. https://www.drupal.org/node/2310173
+      //   We have to tack this onto the node so we can save it later when we
+      //   have a node ID for any new node.
+      $node->menu = $definition;
     }
   }
 }
@@ -573,6 +440,8 @@ function menu_ui_node_submit(EntityInterface $node, $form, $form_state) {
  * @see menu_ui_form_node_type_form_submit().
  */
 function menu_ui_form_node_type_form_alter(&$form, $form_state) {
+  /** @var \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_selector */
+  $menu_parent_selector = \Drupal::service('menu.parent_form_selector');
   $menu_options = menu_ui_get_menus();
   $type = $form_state['controller']->getEntity();
   if ($type->id()) {
@@ -581,7 +450,7 @@ function menu_ui_form_node_type_form_alter(&$form, $form_state) {
   else {
     $config_values = array(
       'available_menus' => array('main'),
-      'parent' => 'main:0',
+      'parent' => 'main:',
     );
   }
   $form['menu'] = array(
@@ -599,12 +468,12 @@ function menu_ui_form_node_type_form_alter(&$form, $form_state) {
     '#options' => $menu_options,
     '#description' => t('The menus available to place links in for this content type.'),
   );
-  // To avoid an 'illegal option' error after saving the form we have to load
-  // all available menu items.
-  // Otherwise it is not possible to dynamically add options to the list.
-  // @todo Convert menu_ui_parent_options() into a #process callback.
-  $menu_link = entity_create('menu_link', array('mlid' => 0));
-  $options = menu_ui_parent_options(menu_ui_get_menus(), $menu_link);
+  // @todo See if we can avoid pre-loading all options by changing the form or
+  //   using a #process callback. https://www.drupal.org/node/2310319
+  //   To avoid an 'illegal option' error after saving the form we have to load
+  //   all available menu parents. Otherwise, it is not possible to dynamically
+  //   add options to the list using ajax.
+  $options = $menu_parent_selector->getParentSelectOptions('');
   $form['menu']['menu_parent'] = array(
     '#type' => 'select',
     '#title' => t('Default parent item'),
@@ -636,7 +505,8 @@ function menu_ui_form_node_type_form_submit(&$form, $form_state) {
  * @param $all
  *   If FALSE return only user-added menus, or if TRUE also include
  *   the menus defined by the system.
- * @return
+ *
+ * @return array
  *   An array with the machine-readable names as the keys, and human-readable
  *   titles as the values.
  */
@@ -661,3 +531,19 @@ function menu_ui_preprocess_block(&$variables) {
     $variables['attributes']['role'] = 'navigation';
   }
 }
+
+
+/**
+ * Implements hook_system_breadcrumb_alter().
+ */
+function menu_ui_system_breadcrumb_alter(array &$breadcrumb, RouteMatchInterface $route_match, array $context) {
+  // Custom breadcrumb behavior for editing menu links, we append a link to
+  // the menu in which the link is found.
+  if (($route_match->getRouteName() == 'menu_ui.link_edit') && $menu_link = $route_match->getParameter('menu_link_plugin')) {
+    if (($menu_link instanceof MenuLinkInterface)) {
+      // Add a link to the menu admin screen.
+      $menu = entity_load('menu', $menu_link->getMenuName());
+      $breadcrumb[] = \Drupal::l($menu->label(), 'menu_ui.menu_edit', array('menu' => $menu->id()));
+    }
+  }
+}
diff --git a/core/modules/menu_ui/menu_ui.routing.yml b/core/modules/menu_ui/menu_ui.routing.yml
index 4be3bbf..b095c34 100644
--- a/core/modules/menu_ui/menu_ui.routing.yml
+++ b/core/modules/menu_ui/menu_ui.routing.yml
@@ -21,37 +21,30 @@ menu_ui.parent_options_js:
   requirements:
     _permission: 'administer menu'
 
-menu_ui.link_add:
-  path: '/admin/structure/menu/manage/{menu}/add'
-  defaults:
-    _content: '\Drupal\menu_ui\Controller\MenuController::addLink'
-    _title: 'Add menu link'
-  requirements:
-    _entity_create_access: 'menu_link'
-
 menu_ui.link_edit:
-  path: '/admin/structure/menu/item/{menu_link}/edit'
+  path: '/admin/structure/menu/link/{menu_link_plugin}/edit'
   defaults:
-    _entity_form: 'menu_link'
+    _form: 'Drupal\menu_ui\Form\MenuLinkEditForm'
     _title: 'Edit menu link'
+  options:
+    parameters:
+      menu_link_plugin:
+        type: menu_link_plugin
   requirements:
-    _entity_access: 'menu_link.update'
+    _permission: 'administer menu'
 
 menu_ui.link_reset:
-  path: '/admin/structure/menu/item/{menu_link}/reset'
+  path: '/admin/structure/menu/link/{menu_link_plugin}/reset'
   defaults:
-    _entity_form: 'menu_link.reset'
+    _form: 'Drupal\menu_ui\Form\MenuLinkResetForm'
     _title: 'Reset menu link'
+  options:
+    parameters:
+      menu_link_plugin:
+        type: menu_link_plugin
   requirements:
-    _entity_access: 'menu_link.reset'
-
-menu_ui.link_delete:
-  path: '/admin/structure/menu/item/{menu_link}/delete'
-  defaults:
-    _entity_form: 'menu_link.delete'
-    _title: 'Delete menu link'
-  requirements:
-    _entity_access: 'menu_link.delete'
+    _permission: 'administer menu'
+    _custom_access: '\Drupal\menu_ui\Form\MenuLinkResetForm::linkIsResettable'
 
 menu_ui.menu_add:
   path: '/admin/structure/menu/add'
diff --git a/core/modules/menu_ui/src/Controller/MenuController.php b/core/modules/menu_ui/src/Controller/MenuController.php
index fad5de5..95788bd 100644
--- a/core/modules/menu_ui/src/Controller/MenuController.php
+++ b/core/modules/menu_ui/src/Controller/MenuController.php
@@ -9,7 +9,9 @@
 
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Menu\MenuParentFormSelectorInterface;
 use Drupal\system\MenuInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -19,6 +21,30 @@
 class MenuController extends ControllerBase {
 
   /**
+   * The menu parent form service.
+   *
+   * @var \Drupal\Core\Menu\MenuParentFormSelectorInterface
+   */
+  protected $menuParentSelector;
+
+  /**
+   * Creates a new MenuController object.
+   *
+   * @param \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_form
+   *   The menu parent form service.
+   */
+  public function __construct(MenuParentFormSelectorInterface $menu_parent_form) {
+    $this->menuParentSelector = $menu_parent_form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('menu.parent_form_selector'));
+  }
+
+  /**
    * Gets all the available menus and menu items as a JavaScript array.
    *
    * @param \Symfony\Component\HttpFoundation\Request $request
@@ -34,30 +60,12 @@ public function getParentOptions(Request $request) {
         $available_menus[$menu] = $menu;
       }
     }
-    $options = _menu_ui_get_options(menu_ui_get_menus(), $available_menus, array('mlid' => 0));
+    $options = $this->menuParentSelector->getParentSelectOptions('', $available_menus);
 
     return new JsonResponse($options);
   }
 
   /**
-   * Provides the menu link submission form.
-   *
-   * @param \Drupal\system\MenuInterface $menu
-   *   An entity representing a custom menu.
-   *
-   * @return array
-   *   Returns the menu link submission form.
-   */
-  public function addLink(MenuInterface $menu) {
-    $menu_link = $this->entityManager()->getStorage('menu_link')->create(array(
-      'mlid' => 0,
-      'plid' => 0,
-      'menu_name' => $menu->id(),
-    ));
-    return $this->entityFormBuilder()->getForm($menu_link);
-  }
-
-  /**
    * Route title callback.
    *
    * @param \Drupal\system\MenuInterface $menu
diff --git a/core/modules/menu_ui/src/Form/MenuDeleteForm.php b/core/modules/menu_ui/src/Form/MenuDeleteForm.php
index 13cbf94..5c3cd96 100644
--- a/core/modules/menu_ui/src/Form/MenuDeleteForm.php
+++ b/core/modules/menu_ui/src/Form/MenuDeleteForm.php
@@ -9,7 +9,7 @@
 
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Entity\EntityConfirmFormBase;
-use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Menu\MenuLinkManagerInterface;
 use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -19,11 +19,11 @@
 class MenuDeleteForm extends EntityConfirmFormBase {
 
   /**
-   * The menu link storage.
+   * The menu link manager.
    *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
+   * @var \Drupal\Core\Menu\MenuLinkManagerInterface
    */
-  protected $storage;
+  protected $menuLinkManager;
 
   /**
    * The database connection.
@@ -35,13 +35,13 @@ class MenuDeleteForm extends EntityConfirmFormBase {
   /**
    * Constructs a new MenuDeleteForm.
    *
-   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
-   *   The menu link storage.
+   * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager
+   *   The menu link manager.
    * @param \Drupal\Core\Database\Connection $connection
    *   The database connection.
    */
-  public function __construct(EntityStorageInterface $storage, Connection $connection) {
-    $this->storage = $storage;
+  public function __construct(MenuLinkManagerInterface $menu_link_manager, Connection $connection) {
+    $this->menuLinkManager = $menu_link_manager;
     $this->connection = $connection;
   }
 
@@ -50,7 +50,7 @@ public function __construct(EntityStorageInterface $storage, Connection $connect
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('entity.manager')->getStorage('menu_link'),
+      $container->get('plugin.manager.menu.link'),
       $container->get('database')
     );
   }
@@ -74,7 +74,7 @@ public function getCancelUrl() {
    */
   public function getDescription() {
     $caption = '';
-    $num_links = $this->storage->countMenuLinks($this->entity->id());
+    $num_links = $this->menuLinkManager->countMenuLinks($this->entity->id());
     if ($num_links) {
       $caption .= '<p>' . format_plural($num_links, '<strong>Warning:</strong> There is currently 1 menu link in %title. It will be deleted (system-defined items will be reset).', '<strong>Warning:</strong> There are currently @count menu links in %title. They will be deleted (system-defined links will be reset).', array('%title' => $this->entity->label())) . '</p>';
     }
@@ -100,21 +100,16 @@ public function submit(array $form, array &$form_state) {
       return;
     }
 
-    // Reset all the menu links defined by the menu_link.static service.
-    $result = \Drupal::entityQuery('menu_link')
-      ->condition('menu_name', $this->entity->id())
-      ->condition('module', '', '>')
-      ->condition('machine_name', '', '>')
-      ->sort('depth', 'ASC')
-      ->execute();
-    $menu_links = $this->storage->loadMultiple($result);
-    foreach ($menu_links as $link) {
-      $link->reset();
-    }
-
     // Delete all links to the overview page for this menu.
-    $menu_links = $this->storage->loadByProperties(array('link_path' => 'admin/structure/menu/manage/' . $this->entity->id()));
-    menu_link_delete_multiple(array_keys($menu_links));
+    // @todo Add a more generic helper function to the menu link plugin
+    //   manager to remove links to a entity or other ID used as a route
+    //   parameter that is being removed. Also, consider moving this to
+    //   menu_ui.module as part of a generic response to entity deletion.
+    //   https://www.drupal.org/node/2310329
+    $menu_links = $this->menuLinkManager->loadLinksByRoute('menu_ui.menu_edit', array('menu' => $this->entity->id()), TRUE);
+    foreach ($menu_links as $id => $link) {
+      $this->menuLinkManager->removeDefinition($id);
+    }
 
     // Delete the custom menu and all its menu links.
     $this->entity->delete();
diff --git a/core/modules/menu_ui/src/Form/MenuLinkDeleteForm.php b/core/modules/menu_ui/src/Form/MenuLinkDeleteForm.php
deleted file mode 100644
index 0a971d9..0000000
--- a/core/modules/menu_ui/src/Form/MenuLinkDeleteForm.php
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_ui\Form\MenuLinkDeleteForm.
- */
-
-namespace Drupal\menu_ui\Form;
-
-use Drupal\Core\Entity\EntityConfirmFormBase;
-use Drupal\Core\Url;
-
-/**
- * Defines a confirmation form for deletion of a single menu link.
- */
-class MenuLinkDeleteForm extends EntityConfirmFormBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getQuestion() {
-    return t('Are you sure you want to delete the custom menu link %item?', array('%item' => $this->entity->link_title));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getCancelUrl() {
-    return new Url('menu_ui.menu_edit', array(
-      'menu' => $this->entity->menu_name,
-    ));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submit(array $form, array &$form_state) {
-    menu_link_delete($this->entity->id());
-    $t_args = array('%title' => $this->entity->link_title);
-    drupal_set_message(t('The menu link %title has been deleted.', $t_args));
-    watchdog('menu', 'Deleted menu link %title.', $t_args, WATCHDOG_NOTICE);
-    $form_state['redirect_route'] = $this->getCancelUrl();
-  }
-
-}
diff --git a/core/modules/menu_ui/src/Form/MenuLinkEditForm.php b/core/modules/menu_ui/src/Form/MenuLinkEditForm.php
index 4e87ffb..cbe096c 100644
--- a/core/modules/menu_ui/src/Form/MenuLinkEditForm.php
+++ b/core/modules/menu_ui/src/Form/MenuLinkEditForm.php
@@ -22,7 +22,7 @@
 class MenuLinkEditForm extends FormBase {
 
   /**
-   * The class resolver
+   * The class resolver.
    *
    * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
    */
diff --git a/core/modules/menu_ui/src/Form/MenuLinkResetForm.php b/core/modules/menu_ui/src/Form/MenuLinkResetForm.php
index b5663aa..6bb22e1 100644
--- a/core/modules/menu_ui/src/Form/MenuLinkResetForm.php
+++ b/core/modules/menu_ui/src/Form/MenuLinkResetForm.php
@@ -7,19 +7,63 @@
 
 namespace Drupal\menu_ui\Form;
 
-use Drupal\Core\Entity\EntityConfirmFormBase;
 use Drupal\Core\Url;
+use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\Core\Menu\MenuLinkManagerInterface;
+use Drupal\Core\Menu\MenuLinkInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Defines a confirmation form for resetting a single modified menu link.
  */
-class MenuLinkResetForm extends EntityConfirmFormBase {
+class MenuLinkResetForm extends ConfirmFormBase {
+
+  /**
+   * The menu link manager.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkManagerInterface
+   */
+  protected $menuLinkManager;
+
+  /**
+   * The menu link.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkInterface
+   */
+  protected $link;
+
+  /**
+   * Constructs a MenuLinkResetForm object.
+   *
+   * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager
+   *   The menu link manager.
+   */
+  public function __construct(MenuLinkManagerInterface $menu_link_manager) {
+    $this->menuLinkManager = $menu_link_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.menu.link')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'menu_link_reset_confirm';
+  }
 
   /**
    * {@inheritdoc}
    */
   public function getQuestion() {
-    return t('Are you sure you want to reset the link %item to its default values?', array('%item' => $this->entity->link_title));
+    return $this->t('Are you sure you want to reset the link %item to its default values?', array('%item' => $this->link->getTitle()));
   }
 
   /**
@@ -27,7 +71,7 @@ public function getQuestion() {
    */
   public function getCancelUrl() {
     return new Url('menu_ui.menu_edit', array(
-      'menu' => $this->entity->menu_name,
+      'menu' => $this->link->getMenuName(),
     ));
   }
 
@@ -35,23 +79,47 @@ public function getCancelUrl() {
    * {@inheritdoc}
    */
   public function getDescription() {
-    return t('Any customizations will be lost. This action cannot be undone.');
+    return $this->t('Any customizations will be lost. This action cannot be undone.');
   }
 
   /**
    * {@inheritdoc}
    */
   public function getConfirmText() {
-    return t('Reset');
+    return $this->t('Reset');
   }
 
   /**
    * {@inheritdoc}
    */
-  public function submit(array $form, array &$form_state) {
-    $this->entity = $this->entity->reset();
-    drupal_set_message(t('The menu link was reset to its default settings.'));
+  public function buildForm(array $form, array &$form_state, MenuLinkInterface $menu_link_plugin = NULL) {
+    $this->link = $menu_link_plugin;
+
+    $form = parent::buildForm($form, $form_state);
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    $this->link = $this->menuLinkManager->resetLink($this->link->getPluginId());
+    drupal_set_message($this->t('The menu link was reset to its default settings.'));
     $form_state['redirect_route'] = $this->getCancelUrl();
   }
 
+  /**
+   * Checks access based on whether the link can be reset.
+   *
+   * @param \Drupal\Core\Menu\MenuLinkInterface $menu_link_plugin
+   *   The menu link plugin being checked.
+   *
+   * @return string
+   *   AccessInterface::ALLOW when access was granted, otherwise
+   *   AccessInterface::DENY.
+   */
+  public function linkIsResettable(MenuLinkInterface $menu_link_plugin) {
+    return $menu_link_plugin->isResettable() ? AccessInterface::ALLOW : AccessInterface::DENY;
+  }
+
 }
diff --git a/core/modules/menu_ui/src/MenuForm.php b/core/modules/menu_ui/src/MenuForm.php
index d854f3c..5e32eaf 100644
--- a/core/modules/menu_ui/src/MenuForm.php
+++ b/core/modules/menu_ui/src/MenuForm.php
@@ -11,9 +11,13 @@
 use Drupal\Core\Entity\EntityForm;
 use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Menu\MenuLinkManagerInterface;
+use Drupal\Core\Menu\MenuLinkTreeElement;
+use Drupal\Core\Menu\MenuLinkTreeInterface;
+use Drupal\Core\Menu\MenuTreeParameters;
 use Drupal\Core\Render\Element;
-use Drupal\menu_link\MenuLinkStorageInterface;
-use Drupal\menu_link\MenuTreeInterface;
+use Drupal\Core\Routing\UrlGeneratorTrait;
+use Drupal\Core\Utility\LinkGeneratorInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -29,20 +33,27 @@ class MenuForm extends EntityForm {
   protected $entityQueryFactory;
 
   /**
-   * The menu link storage.
+   * The menu link manager.
    *
-   * @var \Drupal\menu_link\MenuLinkStorageInterface
+   * @var \Drupal\Core\Menu\MenuLinkManagerInterface
    */
-  protected $menuLinkStorage;
+  protected $menuLinkManager;
 
   /**
    * The menu tree service.
    *
-   * @var \Drupal\menu_link\MenuTreeInterface
+   * @var \Drupal\Core\Menu\MenuLinkTreeInterface
    */
   protected $menuTree;
 
   /**
+   * The link generator.
+   *
+   * @var \Drupal\Core\Utility\LinkGeneratorInterface
+   */
+  protected $linkGenerator;
+
+  /**
    * The overview tree form.
    *
    * @var array
@@ -54,15 +65,18 @@ class MenuForm extends EntityForm {
    *
    * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query_factory
    *   The factory for entity queries.
-   * @param \Drupal\menu_link\MenuLinkStorageInterface $menu_link_storage
-   *   The menu link storage.
-   * @param \Drupal\menu_link\MenuTreeInterface $menu_tree
+   * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager
+   *   The menu link manager.
+   * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
    *   The menu tree service.
+   * @param \Drupal\Core\Utility\LinkGeneratorInterface $link_generator
+   *   The link generator.
    */
-  public function __construct(QueryFactory $entity_query_factory, MenuLinkStorageInterface $menu_link_storage, MenuTreeInterface $menu_tree) {
+  public function __construct(QueryFactory $entity_query_factory, MenuLinkManagerInterface $menu_link_manager, MenuLinkTreeInterface $menu_tree, LinkGeneratorInterface $link_generator) {
     $this->entityQueryFactory = $entity_query_factory;
-    $this->menuLinkStorage = $menu_link_storage;
+    $this->menuLinkManager = $menu_link_manager;
     $this->menuTree = $menu_tree;
+    $this->linkGenerator = $link_generator;
   }
 
   /**
@@ -71,8 +85,9 @@ public function __construct(QueryFactory $entity_query_factory, MenuLinkStorageI
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('entity.query'),
-      $container->get('entity.manager')->getStorage('menu_link'),
-      $container->get('menu_link.tree')
+      $container->get('plugin.manager.menu.link'),
+      $container->get('menu.link_tree'),
+      $container->get('link_generator')
     );
   }
 
@@ -88,16 +103,16 @@ public function form(array $form, array &$form_state) {
 
     $form['label'] = array(
       '#type' => 'textfield',
-      '#title' => t('Title'),
+      '#title' => $this->t('Title'),
       '#default_value' => $menu->label(),
       '#required' => TRUE,
     );
     $form['id'] = array(
       '#type' => 'machine_name',
-      '#title' => t('Menu name'),
+      '#title' => $this->t('Menu name'),
       '#default_value' => $menu->id(),
       '#maxlength' => MENU_MAX_MENU_NAME_LENGTH_UI,
-      '#description' => t('A unique name to construct the URL for the menu. It must only contain lowercase letters, numbers and hyphens.'),
+      '#description' => $this->t('A unique name to construct the URL for the menu. It must only contain lowercase letters, numbers and hyphens.'),
       '#machine_name' => array(
         'exists' => array($this, 'menuNameExists'),
         'source' => array('label'),
@@ -120,28 +135,11 @@ public function form(array $form, array &$form_state) {
       '#languages' => LanguageInterface::STATE_ALL,
       '#default_value' => $menu->language()->getId(),
     );
-    // Unlike the menu langcode, the default language configuration for menu
-    // links only works with language module installed.
-    if ($this->moduleHandler->moduleExists('language')) {
-      $form['default_menu_links_language'] = array(
-        '#type' => 'details',
-        '#title' => t('Menu links language'),
-        '#open' => TRUE,
-      );
-      $form['default_menu_links_language']['default_language'] = array(
-        '#type' => 'language_configuration',
-        '#entity_information' => array(
-          'entity_type' => 'menu_link',
-          'bundle' => $menu->id(),
-        ),
-        '#default_value' => language_get_default_configuration('menu_link', $menu->id()),
-      );
-    }
 
     // Add menu links administration form for existing menus.
     if (!$menu->isNew() || $menu->isLocked()) {
       // Form API supports constructing and validating self-contained sections
-      // within forms, but does not allow to handle the form section's submission
+      // within forms, but does not allow handling the form section's submission
       // equally separated yet. Therefore, we use a $form_state key to point to
       // the parents of the form section.
       // @see self::submitOverviewForm()
@@ -169,41 +167,7 @@ public function menuNameExists($value) {
     }
 
     // Check for a link assigned to this menu.
-    return $this->entityQueryFactory->get('menu_link')->condition('menu_name', $value)->range(0, 1)->count()->execute();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function actions(array $form, array &$form_state) {
-    $actions = parent::actions($form, $form_state);
-
-    // Add the language configuration submit handler. This is needed because the
-    // submit button has custom submit handlers.
-    if ($this->moduleHandler->moduleExists('language')) {
-      array_unshift($actions['submit']['#submit'],'language_configuration_element_submit');
-      array_unshift($actions['submit']['#submit'], array($this, 'languageConfigurationSubmit'));
-    }
-    // We cannot leverage the regular submit handler definition because we have
-    // button-specific ones here. Hence we need to explicitly set it for the
-    // submit action, otherwise it would be ignored.
-    if ($this->moduleHandler->moduleExists('content_translation')) {
-      array_unshift($actions['submit']['#submit'], 'content_translation_language_configuration_element_submit');
-    }
-    return $actions;
-  }
-
-  /**
-   * Submit handler to update the bundle for the default language configuration.
-   */
-  public function languageConfigurationSubmit(array &$form, array &$form_state) {
-    // Since the machine name is not known yet, and it can be changed anytime,
-    // we have to also update the bundle property for the default language
-    // configuration in order to have the correct bundle value.
-    $form_state['language']['default_language']['bundle'] = $form_state['values']['id'];
-    // Clear cache so new menus (bundles) show on the language settings admin
-    // page.
-    \Drupal::entityManager()->clearCachedBundles();
+    return $this->menuLinkManager->menuNameInUse($value);
   }
 
   /**
@@ -217,13 +181,13 @@ public function save(array $form, array &$form_state) {
 
     $status = $menu->save();
 
-    $edit_link = \Drupal::linkGenerator()->generateFromUrl($this->t('Edit'), $this->entity->urlInfo());
+    $edit_link = $this->linkGenerator->generateFromUrl($this->t('Edit'), $this->entity->urlInfo());
     if ($status == SAVED_UPDATED) {
-      drupal_set_message(t('Menu %label has been updated.', array('%label' => $menu->label())));
+      drupal_set_message($this->t('Menu %label has been updated.', array('%label' => $menu->label())));
       watchdog('menu', 'Menu %label has been updated.', array('%label' => $menu->label()), WATCHDOG_NOTICE, $edit_link);
     }
     else {
-      drupal_set_message(t('Menu %label has been added.', array('%label' => $menu->label())));
+      drupal_set_message($this->t('Menu %label has been added.', array('%label' => $menu->label())));
       watchdog('menu', 'Menu %label has been added.', array('%label' => $menu->label()), WATCHDOG_NOTICE, $edit_link);
     }
 
@@ -253,26 +217,30 @@ protected function buildOverviewForm(array &$form, array &$form_state) {
 
     $form['#attached']['css'] = array(drupal_get_path('module', 'menu') . '/css/menu.admin.css');
 
-    $links = array();
-    $query = $this->entityQueryFactory->get('menu_link')
-      ->condition('menu_name', $this->entity->id());
-    for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
-      $query->sort('p' . $i, 'ASC');
-    }
-    $result = $query->execute();
+    $tree = $this->menuTree->load($this->entity->id(), new MenuTreeParameters());
 
-    if (!empty($result)) {
-      $links = $this->menuLinkStorage->loadMultiple($result);
-    }
-
-    $delta = max(count($links), 50);
     // We indicate that a menu administrator is running the menu access check.
     $this->getRequest()->attributes->set('_menu_admin', TRUE);
-    $tree = $this->menuTree->buildTreeData($links);
+    $manipulators = array(
+      array('callable' => 'menu.default_tree_manipulators:checkAccess'),
+      array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
+    );
+    $tree = $this->menuTree->transform($tree, $manipulators);
     $this->getRequest()->attributes->set('_menu_admin', FALSE);
 
+    // Determine the delta; the number of weights to be made available.
+    $count = function(array $tree) {
+      $sum = function ($carry, MenuLinkTreeElement $item) {
+        return $carry + $item->count();
+      };
+      return array_reduce($tree, $sum);
+    };
+    $delta = max($count($tree), 50);
+
     $form = array_merge($form, $this->buildOverviewTreeForm($tree, $delta));
-    $form['#empty_text'] = t('There are no menu links yet. <a href="@link">Add link</a>.', array('@link' => url('admin/structure/menu/manage/' . $this->entity->id() .'/add')));
+    $destination = $this->getUrlGenerator()->getPathFromRoute('menu_ui.menu_edit', array('menu' => $this->entity->id()));
+    $url = $destination = $this->url('menu_link_content.link_add', array('menu' => $this->entity->id()), array('query' => array('destination' => $destination)));
+    $form['#empty_text'] = $this->t('There are no menu links yet. <a href="@url">Add link</a>.', array('@url' => $url));
 
     return $form;
   }
@@ -281,7 +249,7 @@ protected function buildOverviewForm(array &$form, array &$form_state) {
    * Recursive helper function for buildOverviewForm().
    *
    * @param $tree
-   *   The menu_tree retrieved by menu_tree_data.
+   *   The tree retrieved by \Drupal\Core\Menu\MenuLinkTreeInterface::load().
    * @param $delta
    *   The default number of menu items used in the menu weight selector is 50.
    *
@@ -290,70 +258,87 @@ protected function buildOverviewForm(array &$form, array &$form_state) {
    */
   protected function buildOverviewTreeForm($tree, $delta) {
     $form = &$this->overviewTreeForm;
-    foreach ($tree as $data) {
-      $item = $data['link'];
-      // Don't show callbacks; these have $item['hidden'] < 0.
-      if ($item && $item['hidden'] >= 0) {
-        $mlid = 'mlid:' . $item['mlid'];
-        $form[$mlid]['#item'] = $item;
-        $form[$mlid]['#attributes'] = $item['hidden'] ? array('class' => array('menu-disabled')) : array('class' => array('menu-enabled'));
-        $form[$mlid]['title']['#markup'] = l($item['title'], $item['href'], $item['localized_options']);
-        if ($item['hidden']) {
-          $form[$mlid]['title']['#markup'] .= ' (' . t('disabled') . ')';
+    foreach ($tree as $element) {
+      /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
+      $link = $element->link;
+      if ($link) {
+        $id = 'menu_plugin_id:' . $link->getPluginId();
+        $form[$id]['#item'] = $element;
+        $form[$id]['#attributes'] = $link->isHidden() ? array('class' => array('menu-disabled')) : array('class' => array('menu-enabled'));
+        $form[$id]['title']['#markup'] = $this->linkGenerator->generateFromUrl($link->getTitle(), $link->getUrlObject(), $link->getOptions());
+        if ($link->isHidden()) {
+          $form[$id]['title']['#markup'] .= ' (' . $this->t('disabled') . ')';
         }
-        elseif ($item['link_path'] == 'user' && $item['module'] == 'user') {
-          $form[$mlid]['title']['#markup'] .= ' (' . t('logged in users only') . ')';
+        elseif (($url = $link->getUrlObject()) && !$url->isExternal() && $url->getRouteName() == 'user.page') {
+          $form[$id]['title']['#markup'] .= ' (' . $this->t('logged in users only') . ')';
         }
 
-        $form[$mlid]['hidden'] = array(
+        $form[$id]['enabled'] = array(
           '#type' => 'checkbox',
-          '#title' => t('Enable @title menu link', array('@title' => $item['title'])),
+          '#title' => $this->t('Enable @title menu link', array('@title' => $link->getTitle())),
           '#title_display' => 'invisible',
-          '#default_value' => !$item['hidden'],
+          '#default_value' => !$link->isHidden(),
         );
-        $form[$mlid]['weight'] = array(
+        $form[$id]['weight'] = array(
           '#type' => 'weight',
           '#delta' => $delta,
-          '#default_value' => $item['weight'],
-          '#title' => t('Weight for @title', array('@title' => $item['title'])),
+          '#default_value' => $link->getWeight(),
+          '#title' => $this->t('Weight for @title', array('@title' => $link->getTitle())),
           '#title_display' => 'invisible',
         );
-        $form[$mlid]['mlid'] = array(
+        $form[$id]['id'] = array(
           '#type' => 'hidden',
-          '#value' => $item['mlid'],
+          '#value' => $link->getPluginId(),
         );
-        $form[$mlid]['plid'] = array(
+        $form[$id]['parent'] = array(
           '#type' => 'hidden',
-          '#default_value' => $item['plid'],
+          '#default_value' => $link->getParent(),
         );
         // Build a list of operations.
         $operations = array();
         $operations['edit'] = array(
-          'title' => t('Edit'),
-          'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/edit',
+          'title' => $this->t('Edit'),
         );
-        // Only items created by the Menu UI module can be deleted.
-        if ($item->access('delete')) {
-          $operations['delete'] = array(
-            'title' => t('Delete'),
-            'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/delete',
+        // Allow for a custom edit link per plugin.
+        $edit_route = $link->getEditRoute();
+        if ($edit_route) {
+          $operations['edit'] += $edit_route;
+          // Bring the user back to the menu overview.
+          $operations['edit']['query']['destination'] = $this->entity->url();
+        }
+        else {
+          // Fall back to the standard edit link.
+          $operations['edit'] += array(
+            'route_name' => 'menu_ui.link_edit',
+            'route_parameters' => array('menu_link_plugin' => $link->getPluginId()),
           );
         }
-        // Set the reset column.
-        elseif ($item->access('reset')) {
+        // Links can either be reset or deleted, not both.
+        if ($link->isResettable()) {
           $operations['reset'] = array(
-            'title' => t('Reset'),
-            'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/reset',
+            'title' => $this->t('Reset'),
+            'route_name' => 'menu_ui.link_reset',
+            'route_parameters' => array('menu_link_plugin' => $link->getPluginId()),
           );
         }
-        $form[$mlid]['operations'] = array(
+        elseif ($delete_link = $link->getDeleteRoute()) {
+          $operations['delete'] = $delete_link;
+          $operations['delete']['query']['destination'] = $this->entity->url();
+          $operations['delete']['title'] = $this->t('Delete');
+        }
+        if ($link->isTranslatable()) {
+          $operations['translate'] = array(
+            'title' => $this->t('Translate'),
+          ) + (array) $link->getTranslateRoute();
+        }
+        $form[$id]['operations'] = array(
           '#type' => 'operations',
           '#links' => $operations,
         );
       }
 
-      if ($data['below']) {
-        $this->buildOverviewTreeForm($data['below'], $delta);
+      if ($element->subtree) {
+        $this->buildOverviewTreeForm($element->subtree, $delta);
       }
     }
     return $form;
@@ -363,7 +348,7 @@ protected function buildOverviewTreeForm($tree, $delta) {
    * Submit handler for the menu overview form.
    *
    * This function takes great care in saving parent items first, then items
-   * underneath them. Saving items in the incorrect order can break the menu tree.
+   * underneath them. Saving items in the incorrect order can break the tree.
    */
   protected function submitOverviewForm(array $complete_form, array &$form_state) {
     // Form API supports constructing and validating self-contained sections
@@ -384,32 +369,30 @@ protected function submitOverviewForm(array $complete_form, array &$form_state)
     // Update our original form with the new order.
     $form = array_intersect_key(array_merge($order, $form), $form);
 
-    $updated_items = array();
-    $fields = array('weight', 'plid');
-    foreach (Element::children($form) as $mlid) {
-      if (isset($form[$mlid]['#item'])) {
-        $element = $form[$mlid];
+    $fields = array('weight', 'parent', 'enabled');
+    foreach (Element::children($form) as $id) {
+      if (isset($form[$id]['#item'])) {
+        $element = $form[$id];
+        $updated_values = array();
         // Update any fields that have changed in this menu item.
         foreach ($fields as $field) {
           if ($element[$field]['#value'] != $element[$field]['#default_value']) {
-            $element['#item'][$field] = $element[$field]['#value'];
-            $updated_items[$mlid] = $element['#item'];
+            // Hidden is a special case, the form value needs to be reversed.
+            if ($field == 'enabled') {
+              $updated_values['hidden'] = $element['enabled']['#value'] ? 0 : 1;
+            }
+            else {
+              $updated_values[$field] = $element[$field]['#value'];
+            }
           }
         }
-        // Hidden is a special case, the value needs to be reversed.
-        if ($element['hidden']['#value'] != $element['hidden']['#default_value']) {
-          // Convert to integer rather than boolean due to PDO cast to string.
-          $element['#item']['hidden'] = $element['hidden']['#value'] ? 0 : 1;
-          $updated_items[$mlid] = $element['#item'];
+        if ($updated_values) {
+          // Use the ID from the actual plugin instance since the hidden value
+          // in the form could be tampered with.
+          $this->menuLinkManager->updateDefinition($element['#item']->link->getPLuginId(), $updated_values);
         }
       }
     }
-
-    // Save all our changed items to the database.
-    foreach ($updated_items as $item) {
-      $item['customized'] = 1;
-      $item->save();
-    }
   }
 
 }
diff --git a/core/modules/menu_ui/src/Plugin/Menu/LocalAction/MenuLinkAdd.php b/core/modules/menu_ui/src/Plugin/Menu/LocalAction/MenuLinkAdd.php
new file mode 100644
index 0000000..9fee161
--- /dev/null
+++ b/core/modules/menu_ui/src/Plugin/Menu/LocalAction/MenuLinkAdd.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_ui\Plugin\Menu\LocalAction\MenuLinkAdd.
+ */
+
+namespace Drupal\menu_ui\Plugin\Menu\LocalAction;
+
+use Drupal\Core\Menu\LocalActionDefault;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Modifies the 'Add link' local action to add a destination.
+ */
+class MenuLinkAdd extends LocalActionDefault {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOptions(Request $request) {
+    $options = parent::getOptions($request);
+    // Append the current path as destination to the query string.
+    if ($request->attributes->has(RouteObjectInterface::ROUTE_NAME)) {
+      $route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME);
+      $raw_variables = array();
+      if ($request->attributes->has('_raw_variables')) {
+        $raw_variables = $request->attributes->get('_raw_variables')->all();
+      }
+      // @todo Use RouteMatch instead of Request.
+      //   https://www.drupal.org/node/2294157
+      $options['query']['destination'] = \Drupal::urlGenerator()->generateFromRoute($route_name, $raw_variables);
+    }
+    return $options;
+  }
+
+}
diff --git a/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php b/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php
index c863b8d..4a19182 100644
--- a/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php
+++ b/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Contains \Drupal\menu\Tests\MenuCacheTagsTest.
+ * Contains \Drupal\menu_ui\Tests\MenuCacheTagsTest.
  */
 
 namespace Drupal\menu_ui\Tests;
@@ -37,12 +37,10 @@ public function testMenuBlock() {
       'description' => 'Description text',
     ));
     $menu->save();
-    $menu_link = entity_create('menu_link', array(
-      'link_path' => '<front>',
-      'link_title' => 'Vicuña',
-      'menu_name' => 'llama',
-    ));
-    $menu_link->save();
+    /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
+    $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
+    // Move a link into the new menu.
+    $menu_link = $menu_link_manager->updateDefinition('test_page_test.test_page', array('menu_name' => 'llama', 'parent' => ''));
     $block = $this->drupalPlaceBlock('system_menu_block:llama', array('label' => 'Llama', 'provider' => 'system', 'region' => 'footer'));
 
     // Prime the page cache.
@@ -60,7 +58,6 @@ public function testMenuBlock() {
     );
     $this->verifyPageCache($path, 'HIT', $expected_tags);
 
-
     // Verify that after modifying the menu, there is a cache miss.
     $this->pass('Test modification of menu.', 'Debug');
     $menu->label = 'Awesome llama';
@@ -70,23 +67,23 @@ public function testMenuBlock() {
     // Verify a cache hit.
     $this->verifyPageCache($path, 'HIT');
 
-
-    // Verify that after modifying the menu link, there is a cache miss.
+    // Verify that after modifying the menu link weight, there is a cache miss.
+    $menu_link_manager->updateDefinition('test_page_test.test_page', array('weight' => -10));
     $this->pass('Test modification of menu link.', 'Debug');
-    $menu_link->link_title = 'Guanaco';
-    $menu_link->save();
     $this->verifyPageCache($path, 'MISS');
 
     // Verify a cache hit.
     $this->verifyPageCache($path, 'HIT');
 
-
     // Verify that after adding a menu link, there is a cache miss.
     $this->pass('Test addition of menu link.', 'Debug');
-    $menu_link_2 = entity_create('menu_link', array(
-      'link_path' => '<front>',
-      'link_title' => 'Alpaca',
+    $menu_link_2 = entity_create('menu_link_content', array(
+      'id' => '',
+      'parent' => '',
+      'title' => 'Alpaca',
       'menu_name' => 'llama',
+      'route_name' => '<front>',
+      'bundle' => 'menu_name',
     ));
     $menu_link_2->save();
     $this->verifyPageCache($path, 'MISS');
@@ -94,16 +91,15 @@ public function testMenuBlock() {
     // Verify a cache hit.
     $this->verifyPageCache($path, 'HIT');
 
-
-    // Verify that after deleting the first menu link, there is a cache miss.
-    $this->pass('Test deletion of menu link.', 'Debug');
-    $menu_link->delete();
+    // Verify that after resetting the first menu link, there is a cache miss.
+    $this->pass('Test reset of menu link.', 'Debug');
+    $this->assertTrue($menu_link->isResettable(), 'First link can be reset');
+    $menu_link = $menu_link_manager->resetLink($menu_link->getPluginId());
     $this->verifyPageCache($path, 'MISS');
 
     // Verify a cache hit.
     $this->verifyPageCache($path, 'HIT', $expected_tags);
 
-
     // Verify that after deleting the menu, there is a cache miss.
     $this->pass('Test deletion of menu.', 'Debug');
     $menu->delete();
diff --git a/core/modules/menu_ui/src/Tests/MenuLanguageTest.php b/core/modules/menu_ui/src/Tests/MenuLanguageTest.php
index 9267c28..88d9150 100644
--- a/core/modules/menu_ui/src/Tests/MenuLanguageTest.php
+++ b/core/modules/menu_ui/src/Tests/MenuLanguageTest.php
@@ -59,21 +59,12 @@ function testMenuLanguage() {
       'description' => '',
       'label' =>  $label,
       'langcode' => 'aa',
-      'default_language[langcode]' => 'bb',
-      'default_language[language_show]' => TRUE,
     );
     $this->drupalPostForm('admin/structure/menu/add', $edit, t('Save'));
+    language_save_default_configuration('menu_link_content', 'menu_link_content',  array('langcode' => 'bb', 'language_show' => TRUE));
 
-    // Check that the language settings were saved.
-    $this->assertEqual(entity_load('menu', $menu_name)->language()->getId(), $edit['langcode']);
-    $language_settings = language_get_default_configuration('menu_link', $menu_name);
-    $this->assertEqual($language_settings['langcode'], 'bb');
-    $this->assertEqual($language_settings['language_show'], TRUE);
-
-    // Check menu language and item language configuration.
+    // Check menu language.
     $this->assertOptionSelected('edit-langcode', $edit['langcode'], 'The menu language was correctly selected.');
-    $this->assertOptionSelected('edit-default-language-langcode', $edit['default_language[langcode]'], 'The menu link default language was correctly selected.');
-    $this->assertFieldChecked('edit-default-language-language-show');
 
     // Test menu link language.
     $link_path = '<front>';
@@ -81,41 +72,35 @@ function testMenuLanguage() {
     // Add a menu link.
     $link_title = $this->randomString();
     $edit = array(
-      'link_title' => $link_title,
-      'link_path' => $link_path,
+      'title[0][value]' => $link_title,
+      'url' => $link_path,
     );
     $this->drupalPostForm("admin/structure/menu/manage/$menu_name/add", $edit, t('Save'));
     // Check the link was added with the correct menu link default language.
-    $menu_links = entity_load_multiple_by_properties('menu_link', array('link_title' => $link_title));
+    $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $link_title));
     $menu_link = reset($menu_links);
-    $this->assertMenuLink($menu_link->id(), array(
+    $this->assertMenuLink($menu_link->getPluginId(), array(
       'menu_name' => $menu_name,
-      'link_path' => $link_path,
+      'route_name' => '<front>',
       'langcode' => 'bb',
     ));
 
     // Edit menu link default, changing it to cc.
-    $edit = array(
-      'default_language[langcode]' => 'cc',
-    );
-    $this->drupalPostForm("admin/structure/menu/manage/$menu_name", $edit, t('Save'));
-
-    // Check cc is the menu link default.
-    $this->assertOptionSelected('edit-default-language-langcode', $edit['default_language[langcode]'], 'The menu link default language was correctly selected.');
+    language_save_default_configuration('menu_link_content', 'menu_link_content',  array('langcode' => 'cc', 'language_show' => TRUE));
 
     // Add a menu link.
     $link_title = $this->randomString();
     $edit = array(
-      'link_title' => $link_title,
-      'link_path' => $link_path,
+      'title[0][value]' => $link_title,
+      'url' => $link_path,
     );
     $this->drupalPostForm("admin/structure/menu/manage/$menu_name/add", $edit, t('Save'));
     // Check the link was added with the correct new menu link default language.
-    $menu_links = entity_load_multiple_by_properties('menu_link', array('link_title' => $link_title));
+    $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $link_title));
     $menu_link = reset($menu_links);
-    $this->assertMenuLink($menu_link->id(), array(
+    $this->assertMenuLink($menu_link->getPluginId(), array(
       'menu_name' => $menu_name,
-      'link_path' => $link_path,
+      'route_name' => '<front>',
       'langcode' => 'cc',
     ));
 
@@ -124,9 +109,9 @@ function testMenuLanguage() {
       'langcode' => 'bb',
     );
     $this->drupalPostForm('admin/structure/menu/item/' . $menu_link->id() . '/edit', $edit, t('Save'));
-    $this->assertMenuLink($menu_link->id(), array(
+    $this->assertMenuLink($menu_link->getPluginId(), array(
       'menu_name' => $menu_name,
-      'link_path' => $link_path,
+      'route_name' => '<front>',
       'langcode' => 'bb',
     ));
 
@@ -138,16 +123,7 @@ function testMenuLanguage() {
     $this->assertOptionSelected('edit-langcode', 'bb', 'The menu link language was correctly selected.');
 
     // Edit menu to hide the language select on menu link item add.
-    $edit = array(
-      'default_language[language_show]' => FALSE,
-    );
-    $this->drupalPostForm("admin/structure/menu/manage/$menu_name", $edit, t('Save'));
-    $this->assertNoFieldChecked('edit-default-language-language-show');
-
-    // Check that the language settings were saved.
-    $language_settings = language_get_default_configuration('menu_link', $menu_name);
-    $this->assertEqual($language_settings['langcode'], 'cc');
-    $this->assertEqual($language_settings['language_show'], FALSE);
+     language_save_default_configuration('menu_link_content', 'menu_link_content',  array('langcode' => 'cc', 'language_show' => FALSE));
 
     // Check that the language selector is not available on menu link add page.
     $this->drupalGet("admin/structure/menu/manage/$menu_name/add");
diff --git a/core/modules/menu_ui/src/Tests/MenuNodeTest.php b/core/modules/menu_ui/src/Tests/MenuNodeTest.php
index 819bd56..7251397 100644
--- a/core/modules/menu_ui/src/Tests/MenuNodeTest.php
+++ b/core/modules/menu_ui/src/Tests/MenuNodeTest.php
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Definition of Drupal\menu_ui\Tests\MenuNodeTest.
+ * Contains \Drupal\menu_ui\Tests\MenuNodeTest.
  */
 
 namespace Drupal\menu_ui\Tests;
@@ -65,7 +65,7 @@ function testMenuNodeFormWidget() {
     $edit = array(
       'menu_options[main]' => 1,
       'menu_options[tools]' => 1,
-      'menu_parent' => 'main:0',
+      'menu_parent' => 'main:',
     );
     $this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
 
@@ -93,7 +93,7 @@ function testMenuNodeFormWidget() {
     // Edit the node and create a menu link.
     $edit = array(
       'menu[enabled]' => 1,
-      'menu[link_title]' => $node_title,
+      'menu[title]' => $node_title,
       'menu[weight]' => 17,
     );
     $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
@@ -102,7 +102,7 @@ function testMenuNodeFormWidget() {
     $this->assertLink($node_title);
 
     $this->drupalGet('node/' . $node->id() . '/edit');
-    $this->assertOptionSelected('edit-menu-weight', 17, 'Menu weight correct in edit form');
+    $this->assertFieldById('edit-menu-weight', 17, 'Menu weight correct in edit form');
 
     // Edit the node and remove the menu link.
     $edit = array(
@@ -114,10 +114,12 @@ function testMenuNodeFormWidget() {
     $this->assertNoLink($node_title);
 
     // Add a menu link to the Administration menu.
-    $item = entity_create('menu_link', array(
-      'link_path' => 'node/' . $node->id(),
-      'link_title' => $this->randomName(16),
+    $item = entity_create('menu_link_content', array(
+      'route_name' => 'node.view',
+      'route_parameters' => array('node' => $node->id()),
+      'title' => $this->randomName(16),
       'menu_name' => 'admin',
+      'bundle' => 'menu_link_content',
     ));
     $item->save();
 
@@ -127,27 +129,30 @@ function testMenuNodeFormWidget() {
     $this->assertText('Provide a menu link', 'Link in not allowed menu not shown in node edit form');
     // Assert that the link is still in the Administration menu after save.
     $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
-    $link = menu_link_load($item['mlid']);
+    $link = entity_load('menu_link_content', $item->id());
     $this->assertTrue($link, 'Link in not allowed menu still exists after saving node');
 
     // Move the menu link back to the Tools menu.
-    $item['menu_name'] = 'tools';
-    menu_link_save($item);
+    $item->menu_name->value = 'tools';
+    $item->save();
     // Create a second node.
     $child_node = $this->drupalCreateNode(array('type' => 'article'));
     // Assign a menu link to the second node, being a child of the first one.
-    $child_item = entity_create('menu_link', array(
-      'link_path' => 'node/'. $child_node->id(),
-      'link_title' => $this->randomName(16),
-      'plid' => $item['mlid'],
+    $child_item = entity_create('menu_link_content', array(
+      'route_name' => 'node.view',
+      'route_parameters' => array('node' => $child_node->id()),
+      'title' => $this->randomName(16),
+      'parent' => $item->getPluginId(),
+      'menu_name' => $item->getMenuName(),
+      'bundle' => 'menu_link_content',
     ));
     $child_item->save();
     // Edit the first node.
     $this->drupalGet('node/'. $node->id() .'/edit');
     // Assert that it is not possible to set the parent of the first node to itself or the second node.
-    $this->assertNoOption('edit-menu-parent', 'tools:'. $item['mlid']);
-    $this->assertNoOption('edit-menu-parent', 'tools:'. $child_item['mlid']);
+    $this->assertNoOption('edit-menu-menu-parent', 'tools:'. $item->getPluginId());
+    $this->assertNoOption('edit-menu-menu-parent', 'tools:'. $child_item->getPluginId());
     // Assert that unallowed Administration menu is not available in options.
-    $this->assertNoOption('edit-menu-parent', 'admin:0');
+    $this->assertNoOption('edit-menu-menu-parent', 'admin:');
   }
 }
diff --git a/core/modules/menu_ui/src/Tests/MenuTest.php b/core/modules/menu_ui/src/Tests/MenuTest.php
index f707eb9..8fe9e2d 100644
--- a/core/modules/menu_ui/src/Tests/MenuTest.php
+++ b/core/modules/menu_ui/src/Tests/MenuTest.php
@@ -2,12 +2,14 @@
 
 /**
  * @file
- * Definition of Drupal\menu_ui\Tests\MenuTest.
+ * Contains \Drupal\menu_ui\Tests\MenuTest.
  */
 
 namespace Drupal\menu_ui\Tests;
 
 use Drupal\Component\Serialization\Json;
+use Drupal\Core\Menu\MenuLinkInterface;
+use Drupal\menu_link_content\Entity\MenuLinkContent;
 use Drupal\system\Entity\Menu;
 
 /**
@@ -49,7 +51,7 @@ class MenuTest extends MenuWebTestBase {
   /**
    * An array of test menu links.
    *
-   * @var array
+   * @var \Drupal\menu_link_content\Entity\MenuLinkContentInterface[]
    */
   protected $items;
 
@@ -78,17 +80,20 @@ function testMenu() {
 
     // Verify that the menu links rebuild is idempotent and leaves the same
     // number of links in the table.
-    $before_count = db_query('SELECT COUNT(*) FROM {menu_links}')->fetchField();
-    menu_link_rebuild_defaults();
-    $after_count = db_query('SELECT COUNT(*) FROM {menu_links}')->fetchField();
-    $this->assertIdentical($before_count, $after_count, 'menu_link_rebuild_defaults() does not add more links');
+    /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
+    $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
+    $before_count = $menu_link_manager->countMenuLinks(NULL);
+    $menu_link_manager->rebuild();
+    $after_count = $menu_link_manager->countMenuLinks(NULL);
+    $this->assertIdentical($before_count, $after_count, 'MenuLinkManager::rebuild() does not add more links');
     // Do standard user tests.
     // Login the user.
     $this->drupalLogin($this->authenticated_user);
     $this->verifyAccess(403);
+
     foreach ($this->items as $item) {
       // Paths were set as 'node/$nid'.
-      $node = node_load(substr($item['link_path'], 5));
+      $node = node_load($item->getRouteParameters()['node']);
       $this->verifyMenuLink($item, $node);
     }
 
@@ -104,20 +109,20 @@ function testMenu() {
     $this->deleteCustomMenu();
 
     // Modify and reset a standard menu link.
-    $item = $this->getStandardMenuLink();
-    $old_title = $item['link_title'];
-    $this->modifyMenuLink($item);
-    $item = entity_load('menu_link', $item['mlid']);
-    // Verify that a change to the description is saved.
-    $description = $this->randomName(16);
-    $item['options']['attributes']['title']  = $description;
-    $return_value = menu_link_save($item);
-    // Save the menu link again to test the return value of the procedural save
-    // helper.
-    $this->assertIdentical($return_value, $item->save(), 'Return value of menu_link_save() is identical to the return value of $menu_link->save().');
-    $saved_item = entity_load('menu_link', $item['mlid']);
-    $this->assertEqual($description, $saved_item['options']['attributes']['title'], 'Saving an existing link updates the description (title attribute)');
-    $this->resetMenuLink($item, $old_title);
+    $instance = $this->getStandardMenuLink();
+    $old_weight = $instance->getWeight();
+    // Edit the static menu link.
+    $edit = array();
+    $edit['weight'] = 10;
+    $id = $instance->getPluginId();
+    $this->drupalPostForm("admin/structure/menu/link/$id/edit", $edit, t('Save'));
+    $this->assertResponse(200);
+    $this->assertText('The menu link has been saved.');
+    $menu_link_manager->resetDefinitions();
+
+    $instance = $menu_link_manager->createInstance($instance->getPluginId());
+    $this->assertEqual($edit['weight'], $instance->getWeight(), 'Saving an existing link updates the weight.');
+    $this->resetMenuLink($instance, $old_weight);
   }
 
   /**
@@ -214,7 +219,7 @@ function deleteCustomMenu() {
     $this->assertRaw(t('The custom menu %title has been deleted.', array('%title' => $label)), 'Custom menu was deleted');
     $this->assertNull(Menu::load($menu_name), 'Custom menu was deleted');
     // Test if all menu links associated to the menu were removed from database.
-    $result = entity_load_multiple_by_properties('menu_link', array('menu_name' => $menu_name));
+    $result = entity_load_multiple_by_properties('menu_link_content', array('menu_name' => $menu_name));
     $this->assertFalse($result, 'All menu links associated to the custom menu were deleted.');
 
     // Make sure there's no delete button on system menus.
@@ -245,33 +250,32 @@ function doMenuTests() {
     ));
 
     // Add menu links.
-    $item1 = $this->addMenuLink(0, 'node/' . $node1->id(), $menu_name);
-    $item2 = $this->addMenuLink($item1['mlid'], 'node/' . $node2->id(), $menu_name, FALSE);
-    $item3 = $this->addMenuLink($item2['mlid'], 'node/' . $node3->id(), $menu_name);
-    $this->assertMenuLink($item1['mlid'], array(
-      'depth' => 1,
-      'has_children' => 1,
-      'p1' => $item1['mlid'],
-      'p2' => 0,
+    $item1 = $this->addMenuLink('', 'node/' . $node1->id(), $menu_name, TRUE);
+    $item2 = $this->addMenuLink($item1->getPluginId(), 'node/' . $node2->id(), $menu_name, FALSE);
+    $item3 = $this->addMenuLink($item2->getPluginId(), 'node/' . $node3->id(), $menu_name);
+
+    // Hierarchy
+    // <$menu_name>
+    // - item1
+    // -- item2
+    // --- item3
+
+    $this->assertMenuLink($item1->getPluginId(), array(
+      'children' => array($item2->getPluginId(), $item3->getPluginId()),
+      'parents' => array($item1->getPluginId()),
       // We assert the language code here to make sure that the language
       // selection element degrades gracefully without the Language module.
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item2['mlid'], array(
-      'depth' => 2, 'has_children' => 1,
-      'p1' => $item1['mlid'],
-      'p2' => $item2['mlid'],
-      'p3' => 0,
+    $this->assertMenuLink($item2->getPluginId(), array(
+      'children' => array($item3->getPluginId()),
+      'parents' => array($item2->getPluginId(), $item1->getPluginId()),
       // See above.
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item3['mlid'], array(
-      'depth' => 3,
-      'has_children' => 0,
-      'p1' => $item1['mlid'],
-      'p2' => $item2['mlid'],
-      'p3' => $item3['mlid'],
-      'p4' => 0,
+    $this->assertMenuLink($item3->getPluginId(), array(
+      'children' => array(),
+      'parents' => array($item3->getPluginId(), $item2->getPluginId(), $item1->getPluginId()),
       // See above.
       'langcode' => 'en',
     ));
@@ -282,34 +286,37 @@ function doMenuTests() {
     $this->verifyMenuLink($item3, $node3, $item2, $node2);
 
     // Add more menu links.
-    $item4 = $this->addMenuLink(0, 'node/' . $node4->id(), $menu_name);
-    $item5 = $this->addMenuLink($item4['mlid'], 'node/' . $node5->id(), $menu_name);
+    $item4 = $this->addMenuLink('', 'node/' . $node4->id(), $menu_name);
+    $item5 = $this->addMenuLink($item4->getPluginId(), 'node/' . $node5->id(), $menu_name);
     // Create a menu link pointing to an alias.
-    $item6 = $this->addMenuLink($item4['mlid'], 'node5', $menu_name, TRUE, '0', 'node/' . $node5->id());
-    $this->assertMenuLink($item4['mlid'], array(
-      'depth' => 1,
-      'has_children' => 1,
-      'p1' => $item4['mlid'],
-      'p2' => 0,
+    $item6 = $this->addMenuLink($item4->getPluginId(), 'node5', $menu_name, TRUE, '0');
+
+    // Hierarchy
+    // <$menu_name>
+    // - item1
+    // -- item2
+    // --- item3
+    // - item4
+    // -- item5
+    // -- item6
+
+    $this->assertMenuLink($item4->getPluginId(), array(
+      'children' => array($item5->getPluginId(), $item6->getPluginId()),
+      'parents' => array($item4->getPluginId()),
       // See above.
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item5['mlid'], array(
-      'depth' => 2,
-      'has_children' => 0,
-      'p1' => $item4['mlid'],
-      'p2' => $item5['mlid'],
-      'p3' => 0,
-      // See above.
+    $this->assertMenuLink($item5->getPluginId(), array(
+      'children' => array(),
+      'parents' => array($item5->getPluginId(), $item4->getPluginId()),
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item6['mlid'], array(
-      'depth' => 2,
-      'has_children' => 0,
-      'p1' => $item4['mlid'],
-      'p2' => $item6['mlid'],
-      'p3' => 0,
-      'link_path' => 'node/' . $node5->id(),
+    $this->assertMenuLink($item6->getPluginId(), array(
+      'children' => array(),
+      'parents' => array($item6->getPluginId(), $item4->getPluginId()),
+      'route_name' => 'node.view',
+      'route_parameters' => array('node' => $node5->id()),
+      'url' => '',
       // See above.
       'langcode' => 'en',
     ));
@@ -323,50 +330,44 @@ function doMenuTests() {
     $this->toggleMenuLink($item2);
 
     // Move link and verify that descendants are updated.
-    $this->moveMenuLink($item2, $item5['mlid'], $menu_name);
-    $this->assertMenuLink($item1['mlid'], array(
-      'depth' => 1,
-      'has_children' => 0,
-      'p1' => $item1['mlid'],
-      'p2' => 0,
+    $this->moveMenuLink($item2, $item5->getPluginId(), $menu_name);
+    // Hierarchy
+    // <$menu_name>
+    // - item1 (disabled)
+    // - item4
+    // -- item5
+    // --- item2 (disabled)
+    // ---- item3
+    // -- item6
+
+    $this->assertMenuLink($item1->getPluginId(), array(
+      'children' => array(),
+      'parents' => array($item1->getPluginId()),
       // See above.
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item4['mlid'], array(
-      'depth' => 1,
-      'has_children' => 1,
-      'p1' => $item4['mlid'],
-      'p2' => 0,
+    $this->assertMenuLink($item4->getPluginId(), array(
+      'children' => array($item5->getPluginId(), $item6->getPluginId(), $item2->getPluginId(), $item3->getPluginId()),
+      'parents' => array($item4->getPluginId()),
       // See above.
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item5['mlid'], array(
-      'depth' => 2,
-      'has_children' => 1,
-      'p1' => $item4['mlid'],
-      'p2' => $item5['mlid'],
-      'p3' => 0,
+
+    $this->assertMenuLink($item5->getPluginId(), array(
+      'children' => array($item2->getPluginId(), $item3->getPluginId()),
+      'parents' => array($item5->getPluginId(), $item4->getPluginId()),
       // See above.
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item2['mlid'], array(
-      'depth' => 3,
-      'has_children' => 1,
-      'p1' => $item4['mlid'],
-      'p2' => $item5['mlid'],
-      'p3' => $item2['mlid'],
-      'p4' => 0,
+    $this->assertMenuLink($item2->getPluginId(), array(
+      'children' => array($item3->getPluginId()),
+      'parents' => array($item2->getPluginId(), $item5->getPluginId(), $item4->getPluginId()),
       // See above.
       'langcode' => 'en',
     ));
-    $this->assertMenuLink($item3['mlid'], array(
-      'depth' => 4,
-      'has_children' => 0,
-      'p1' => $item4['mlid'],
-      'p2' => $item5['mlid'],
-      'p3' => $item2['mlid'],
-      'p4' => $item3['mlid'],
-      'p5' => 0,
+    $this->assertMenuLink($item3->getPluginId(), array(
+      'children' => array(),
+      'parents' => array($item3->getPluginId(), $item2->getPluginId(), $item5->getPluginId(), $item4->getPluginId()),
       // See above.
       'langcode' => 'en',
     ));
@@ -375,33 +376,31 @@ function doMenuTests() {
     // item's weight doesn't get changed because of the old hardcoded delta=50.
     $items = array();
     for ($i = -50; $i <= 51; $i++) {
-      $items[$i] = $this->addMenuLink(0, 'node/' . $node1->id(), $menu_name, TRUE, strval($i));
+      $items[$i] = $this->addMenuLink('', 'node/' . $node1->id(), $menu_name, TRUE, strval($i));
     }
-    $this->assertMenuLink($items[51]['mlid'], array('weight' => '51'));
+    $this->assertMenuLink($items[51]->getPluginId(), array('weight' => '51'));
 
     // Enable a link via the overview form.
     $this->disableMenuLink($item1);
     $edit = array();
 
-    // Note in the UI the 'links[mlid:x][hidden]' form element maps to enabled,
-    // or NOT hidden.
-    $edit['links[mlid:' . $item1['mlid'] . '][hidden]'] = TRUE;
-    $this->drupalPostForm('admin/structure/menu/manage/' . $item1['menu_name'], $edit, t('Save'));
+    $edit['links[menu_plugin_id:' . $item1->getPluginId() . '][enabled]'] = TRUE;
+    $this->drupalPostForm('admin/structure/menu/manage/' . $item1->getMenuName(), $edit, t('Save'));
 
     // Verify in the database.
-    $this->assertMenuLink($item1['mlid'], array('hidden' => 0));
+    $this->assertMenuLink($item1->getPluginId(), array('hidden' => 0));
 
     // Add an external link.
-    $item7 = $this->addMenuLink(0, 'http://drupal.org', $menu_name);
-    $this->assertMenuLink($item7['mlid'], array('link_path' => 'http://drupal.org', 'external' => 1));
+    $item7 = $this->addMenuLink('', 'http://drupal.org', $menu_name);
+    $this->assertMenuLink($item7->getPluginId(), array('url' => 'http://drupal.org'));
 
     // Add <front> menu item.
-    $item8 = $this->addMenuLink(0, '<front>', $menu_name);
-    $this->assertMenuLink($item8['mlid'], array('link_path' => '<front>', 'external' => 1));
+    $item8 = $this->addMenuLink('', '<front>', $menu_name);
+    $this->assertMenuLink($item8->getPluginId(), array('route_name' => '<front>'));
     $this->drupalGet('');
     $this->assertResponse(200);
     // Make sure we get routed correctly.
-    $this->clickLink($item8['link_title']);
+    $this->clickLink($item8->getTitle());
     $this->assertResponse(200);
 
     // Save menu links for later tests.
@@ -417,16 +416,16 @@ function testMenuQueryAndFragment() {
 
     // Make a path with query and fragment on.
     $path = 'test-page?arg1=value1&arg2=value2';
-    $item = $this->addMenuLink(0, $path);
+    $item = $this->addMenuLink('', $path);
 
-    $this->drupalGet('admin/structure/menu/item/' . $item['mlid'] . '/edit');
-    $this->assertFieldByName('link_path', $path, 'Path is found with both query and fragment.');
+    $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit');
+    $this->assertFieldByName('url', $path, 'Path is found with both query and fragment.');
 
     // Now change the path to something without query and fragment.
     $path = 'test-page';
-    $this->drupalPostForm('admin/structure/menu/item/' . $item['mlid'] . '/edit', array('link_path' => $path), t('Save'));
-    $this->drupalGet('admin/structure/menu/item/' . $item['mlid'] . '/edit');
-    $this->assertFieldByName('link_path', $path, 'Path no longer has query or fragment.');
+    $this->drupalPostForm('admin/structure/menu/item/' . $item->id() . '/edit', array('url' => $path), t('Save'));
+    $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit');
+    $this->assertFieldByName('url', $path, 'Path no longer has query or fragment.');
   }
 
   /**
@@ -456,15 +455,15 @@ function testUnpublishedNodeMenuItem() {
       'status' => NODE_NOT_PUBLISHED,
     ));
 
-    $item = $this->addMenuLink(0, 'node/' . $node->id());
+    $item = $this->addMenuLink('', 'node/' . $node->id());
     $this->modifyMenuLink($item);
 
     // Test that a user with 'administer menu' but without 'bypass node access'
     // cannot see the menu item.
     $this->drupalLogout();
     $this->drupalLogin($this->admin_user);
-    $this->drupalGet('admin/structure/menu/manage/' . $item['menu_name']);
-    $this->assertNoText($item['link_title'], "Menu link pointing to unpublished node is only visible to users with 'bypass node access' permission");
+    $this->drupalGet('admin/structure/menu/manage/' . $item->getMenuName());
+    $this->assertNoText($item->getTitle(), "Menu link pointing to unpublished node is only visible to users with 'bypass node access' permission");
   }
 
   /**
@@ -490,42 +489,12 @@ public function testBlockContextualLinks() {
   }
 
   /**
-   * Tests menu link bundles.
-   */
-  public function testMenuBundles() {
-    $this->drupalLogin($this->admin_user);
-    $menu = $this->addCustomMenu();
-    // Clear the entity cache to ensure the static caches are rebuilt.
-    \Drupal::entityManager()->clearCachedBundles();
-    $bundles = entity_get_bundles('menu_link');
-    $this->assertTrue(isset($bundles[$menu->id()]));
-    $menus = menu_list_system_menus();
-    $menus[$menu->id()] = $menu->label();
-    ksort($menus);
-    $this->assertIdentical(array_keys($bundles), array_keys($menus));
-
-    // Test if moving a menu link between menus changes the bundle.
-    $node = $this->drupalCreateNode(array('type' => 'article'));
-    $item = $this->addMenuLink(0, 'node/' . $node->id(), 'tools');
-    $this->moveMenuLink($item, 0, $menu->id());
-    $this->assertEqual($item->bundle(), 'tools', 'Menu link bundle matches the menu');
-
-    $moved_item = entity_load('menu_link', $item->id(), TRUE);
-    $this->assertNotEqual($moved_item->bundle(), $item->bundle(), 'Menu link bundle was changed');
-    $this->assertEqual($moved_item->bundle(), $menu->id(), 'Menu link bundle matches the menu');
-
-    $unsaved_item = entity_create('menu_link', array('menu_name' => $menu->id(), 'link_title' => $this->randomName(16), 'link_path' => '<front>'));
-    $this->assertEqual($unsaved_item->bundle(), $menu->id(), 'Unsaved menu link bundle matches the menu');
-    $this->assertEqual($unsaved_item->menu_name, $menu->id(), 'Unsaved menu link menu name matches the menu');
-  }
-
-  /**
    * Adds a menu link using the UI.
    *
-   * @param integer $plid
+   * @param string $parent
    *   Optional parent menu link id.
-   * @param string $link
-   *   Link path. Defaults to the front page.
+   * @param string $path
+   *   The path to enter on the form. Defaults to the front page.
    * @param string $menu_name
    *   Menu name. Defaults to 'tools'.
    * @param bool $expanded
@@ -534,40 +503,36 @@ public function testMenuBundles() {
    *   to FALSE.
    * @param string $weight
    *  Menu weight. Defaults to 0.
-   * @param string $actual_link
-   *   Actual link path in case $link is an alias.
    *
-   * @return \Drupal\menu_link\Entity\MenuLink
+   * @return \Drupal\menu_link_content\Entity\MenuLinkContent
    *   A menu link entity.
    */
-  function addMenuLink($plid = 0, $link = '<front>', $menu_name = 'tools', $expanded = TRUE, $weight = '0', $actual_link = FALSE) {
+  function addMenuLink($parent = '', $path = '<front>', $menu_name = 'tools', $expanded = FALSE, $weight = '0') {
     // View add menu link page.
     $this->drupalGet("admin/structure/menu/manage/$menu_name/add");
     $this->assertResponse(200);
 
     $title = '!link_' . $this->randomName(16);
     $edit = array(
-      'link_path' => $link,
-      'link_title' => $title,
-      'description' => '',
-      'enabled' => TRUE,
-      'expanded' => $expanded,
-      'parent' =>  $menu_name . ':' . $plid,
-      'weight' => $weight,
+      'url' => $path,
+      'title[0][value]' => $title,
+      'description[0][value]' => '',
+      'enabled' => 1,
+      'expanded[value]' => $expanded,
+      'menu_parent' =>  $menu_name . ':' . $parent,
+      'weight[0][value]' => $weight,
     );
 
-    if (!$actual_link) {
-      $actual_link = $link;
-    }
     // Add menu link.
     $this->drupalPostForm(NULL, $edit, t('Save'));
     $this->assertResponse(200);
     $this->assertText('The menu link has been saved.');
 
-    $menu_links = entity_load_multiple_by_properties('menu_link', array('link_title' => $title));
+    $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $title));
+
     $menu_link = reset($menu_links);
     $this->assertTrue($menu_link, 'Menu link was found in database.');
-    $this->assertMenuLink($menu_link->id(), array('menu_name' => $menu_name, 'link_path' => $actual_link, 'has_children' => 0, 'plid' => $plid));
+    $this->assertMenuLink($menu_link->getPluginId(), array('menu_name' => $menu_name, 'children' => array(), 'parent' => $parent));
 
     return $menu_link;
   }
@@ -578,27 +543,27 @@ function addMenuLink($plid = 0, $link = '<front>', $menu_name = 'tools', $expand
   function addInvalidMenuLink() {
     foreach (array('-&-', 'admin/people/permissions', '#') as $link_path) {
       $edit = array(
-        'link_path' => $link_path,
-        'link_title' => 'title',
+        'url' => $link_path,
+        'title[0][value]' => 'title',
       );
       $this->drupalPostForm("admin/structure/menu/manage/{$this->menu->id()}/add", $edit, t('Save'));
-      $this->assertRaw(t("The path '@path' is either invalid or you do not have access to it.", array('@path' => $link_path)), 'Menu link was not created');
+      $this->assertRaw(t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $link_path)), 'Menu link was not created');
     }
   }
 
   /**
    * Verifies a menu link using the UI.
    *
-   * @param array $item
+   * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
    *   Menu link.
    * @param object $item_node
    *   Menu link content node.
-   * @param array $parent
+   * @param \Drupal\menu_link_content\Entity\MenuLinkContent $parent
    *   Parent menu link.
    * @param object $parent_node
    *   Parent menu link content node.
    */
-  function verifyMenuLink($item, $item_node, $parent = NULL, $parent_node = NULL) {
+  function verifyMenuLink(MenuLinkContent $item, $item_node, MenuLinkContent $parent = NULL, $parent_node = NULL) {
     // View home page.
     $this->drupalGet('');
     $this->assertResponse(200);
@@ -606,7 +571,7 @@ function verifyMenuLink($item, $item_node, $parent = NULL, $parent_node = NULL)
     // Verify parent menu link.
     if (isset($parent)) {
       // Verify menu link.
-      $title = $parent['link_title'];
+      $title = $parent->getTitle();
       $this->assertLink($title, 0, 'Parent menu link was displayed');
 
       // Verify menu link link.
@@ -616,7 +581,7 @@ function verifyMenuLink($item, $item_node, $parent = NULL, $parent_node = NULL)
     }
 
     // Verify menu link.
-    $title = $item['link_title'];
+    $title = $item->getTitle();
     $this->assertLink($title, 0, 'Menu link was displayed');
 
     // Verify menu link link.
@@ -628,18 +593,18 @@ function verifyMenuLink($item, $item_node, $parent = NULL, $parent_node = NULL)
   /**
    * Changes the parent of a menu link using the UI.
    *
-   * @param array $item
+   * @param \Drupal\menu_link_content\Entity\MenuLinkContentInterface $item
    *   The menu link item to move.
-   * @param int $plid
+   * @param int $parent
    *   The id of the new parent.
    * @param string $menu_name
    *   The menu the menu link will be moved to.
    */
-  function moveMenuLink($item, $plid, $menu_name) {
-    $mlid = $item['mlid'];
+  function moveMenuLink(MenuLinkContent $item, $parent, $menu_name) {
+    $mlid = $item->id();
 
     $edit = array(
-      'parent' => $menu_name . ':' . $plid,
+      'menu_parent' => $menu_name . ':' . $parent,
     );
     $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
     $this->assertResponse(200);
@@ -648,58 +613,54 @@ function moveMenuLink($item, $plid, $menu_name) {
   /**
    * Modifies a menu link using the UI.
    *
-   * @param array $item
-   *   Menu link passed by reference.
+   * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
+   *   Menu link entity.
    */
-  function modifyMenuLink(&$item) {
-    $item['link_title'] = $this->randomName(16);
+  function modifyMenuLink(MenuLinkContent $item) {
+    $item->title->value = $this->randomName(16);
 
-    $mlid = $item['mlid'];
-    $title = $item['link_title'];
+    $mlid = $item->id();
+    $title = $item->getTitle();
 
     // Edit menu link.
     $edit = array();
-    $edit['link_title'] = $title;
+    $edit['title[0][value]'] = $title;
     $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
     $this->assertResponse(200);
     $this->assertText('The menu link has been saved.');
     // Verify menu link.
-    $this->drupalGet('admin/structure/menu/manage/' . $item['menu_name']);
+    $this->drupalGet('admin/structure/menu/manage/' . $item->getMenuName());
     $this->assertText($title, 'Menu link was edited');
   }
 
   /**
    * Resets a standard menu link using the UI.
    *
-   * @param array $item
-   *   Menu link.
-   * @param string $old_title
+   * @param \Drupal\Core\Menu\MenuLinkInterface $menu_link
+   *   The Menu link.
+   * @param int $old_weight
    *   Original title for menu link.
    */
-  function resetMenuLink($item, $old_title) {
-    $mlid = $item['mlid'];
-    $title = $item['link_title'];
-
+  function resetMenuLink(MenuLinkInterface $menu_link, $old_weight) {
     // Reset menu link.
-    $this->drupalPostForm("admin/structure/menu/item/$mlid/reset", array(), t('Reset'));
+    $this->drupalPostForm("admin/structure/menu/link/{$menu_link->getPluginId()}/reset", array(), t('Reset'));
     $this->assertResponse(200);
     $this->assertRaw(t('The menu link was reset to its default settings.'), 'Menu link was reset');
 
     // Verify menu link.
-    $this->drupalGet('');
-    $this->assertNoText($title, 'Menu link was reset');
-    $this->assertText($old_title, 'Menu link was reset');
+    $instance = \Drupal::service('plugin.manager.menu.link')->createInstance($menu_link->getPluginId());
+    $this->assertEqual($old_weight, $instance->getWeight(), 'Resets to the old weight.');
   }
 
   /**
    * Deletes a menu link using the UI.
    *
-   * @param array $item
+   * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
    *   Menu link.
    */
-  function deleteMenuLink($item) {
-    $mlid = $item['mlid'];
-    $title = $item['link_title'];
+  function deleteMenuLink(MenuLinkContent $item) {
+    $mlid = $item->id();
+    $title = $item->getTitle();
 
     // Delete menu link.
     $this->drupalPostForm("admin/structure/menu/item/$mlid/delete", array(), t('Confirm'));
@@ -714,51 +675,51 @@ function deleteMenuLink($item) {
   /**
    * Alternately disables and enables a menu link.
    *
-   * @param $item
+   * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
    *   Menu link.
    */
-  function toggleMenuLink($item) {
+  function toggleMenuLink(MenuLinkContent $item) {
     $this->disableMenuLink($item);
 
     // Verify menu link is absent.
     $this->drupalGet('');
-    $this->assertNoText($item['link_title'], 'Menu link was not displayed');
+    $this->assertNoText($item->getTitle(), 'Menu link was not displayed');
     $this->enableMenuLink($item);
 
     // Verify menu link is displayed.
     $this->drupalGet('');
-    $this->assertText($item['link_title'], 'Menu link was displayed');
+    $this->assertText($item->getTitle(), 'Menu link was displayed');
   }
 
   /**
    * Disables a menu link.
    *
-   * @param $item
+   * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
    *   Menu link.
    */
-  function disableMenuLink($item) {
-    $mlid = $item['mlid'];
+  function disableMenuLink(MenuLinkContent $item) {
+    $mlid = $item->id();
     $edit['enabled'] = FALSE;
     $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
 
     // Unlike most other modules, there is no confirmation message displayed.
     // Verify in the database.
-    $this->assertMenuLink($mlid, array('hidden' => 1));
+    $this->assertMenuLink($item->getPluginId(), array('hidden' => 1));
   }
 
   /**
    * Enables a menu link.
    *
-   * @param $item
+   * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
    *   Menu link.
    */
-  function enableMenuLink($item) {
-    $mlid = $item['mlid'];
+  function enableMenuLink(MenuLinkContent $item) {
+    $mlid = $item->id();
     $edit['enabled'] = TRUE;
     $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
 
     // Verify in the database.
-    $this->assertMenuLink($mlid, array('hidden' => 0));
+    $this->assertMenuLink($item->getPluginId(), array('hidden' => 0));
   }
 
   /**
@@ -782,27 +743,19 @@ public function testMenuParentsJsAccess() {
   /**
    * Returns standard menu link.
    *
-   * @return \Drupal\menu_link\Entity\MenuLink
-   *   A menu link entity.
+   * @return \Drupal\Core\Menu\MenuLinkInterface
+   *   A menu link plugin.
    */
   private function getStandardMenuLink() {
-    $mlid = 0;
     // Retrieve menu link id of the Log out menu link, which will always be on
     // the front page.
-    $query = \Drupal::entityQuery('menu_link')
-      ->condition('module', 'user')
-      ->condition('machine_name', 'user.logout');
-    $result = $query->execute();
-    if (!empty($result)) {
-      $mlid = reset($result);
-    }
+    /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
+    $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
+    $result = $menu_link_manager->loadLinksByRoute('user.logout');
+    $instance = reset($result);
 
-    $this->assertTrue($mlid > 0, 'Standard menu link id was found');
-    // Load menu link.
-    // Use api function so that link is translated for rendering.
-    $item = entity_load('menu_link', $mlid);
-    $this->assertTrue((bool) $item, 'Standard menu link was loaded');
-    return $item;
+    $this->assertTrue((bool) $instance, 'Standard menu link was loaded');
+    return $instance;
   }
 
   /**
@@ -833,9 +786,9 @@ private function verifyAccess($response = 200) {
       $this->assertText(t('Tools'), 'Tools menu page was displayed');
     }
 
-    // View menu edit page.
+    // View menu edit page for a static link.
     $item = $this->getStandardMenuLink();
-    $this->drupalGet('admin/structure/menu/item/' . $item['mlid'] . '/edit');
+    $this->drupalGet('admin/structure/menu/link/' . $item->getPluginId() . '/edit');
     $this->assertResponse($response);
     if ($response == 200) {
       $this->assertText(t('Edit menu item'), 'Menu edit page was displayed');
diff --git a/core/modules/menu_ui/src/Tests/MenuWebTestBase.php b/core/modules/menu_ui/src/Tests/MenuWebTestBase.php
index 5f16536..6069197 100644
--- a/core/modules/menu_ui/src/Tests/MenuWebTestBase.php
+++ b/core/modules/menu_ui/src/Tests/MenuWebTestBase.php
@@ -19,28 +19,58 @@
    *
    * @var array
    */
-  public static $modules = array('menu_ui');
+  public static $modules = array('menu_ui', 'menu_link_content');
 
   /**
    * Fetchs the menu item from the database and compares it to expected item.
    *
-   * @param int $mlid
+   * @param int $menu_plugin_id
    *   Menu item id.
-   * @param array $item
+   * @param array $expected_item
    *   Array containing properties to verify.
    */
-  function assertMenuLink($mlid, array $expected_item) {
+  function assertMenuLink($menu_plugin_id, array $expected_item) {
     // Retrieve menu link.
-    $item = entity_load('menu_link', $mlid);
-    $options = $item->options;
-    if (!empty($options['query'])) {
-      $item['link_path'] .= '?' . \Drupal::urlGenerator()->httpBuildQuery($options['query']);
+    /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
+    $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
+    $menu_link_manager->resetDefinitions();
+    // Reset the static load cache.
+    \Drupal::entityManager()->getStorage('menu_link_content')->resetCache();
+    $definition = $menu_link_manager->getDefinition($menu_plugin_id);
+
+    $entity = NULL;
+
+    // Pull the path from the menu link content.
+    if (strpos($menu_plugin_id, 'menu_link_content') === 0) {
+      list(, $uuid) = explode(':', $menu_plugin_id, 2);
+      /** @var \Drupal\menu_link_content\Entity\MenuLinkContent $entity */
+      $entity = \Drupal::entityManager()->loadEntityByUuid('menu_link_content', $uuid);
+    }
+
+    if (isset($expected_item['children'])) {
+      $child_ids = array_values($menu_link_manager->getChildIds($menu_plugin_id));
+      sort($expected_item['children']);
+      if ($child_ids) {
+        sort($child_ids);
+      }
+      $this->assertEqual($expected_item['children'], $child_ids);
+      unset($expected_item['children']);
     }
-    if (!empty($options['fragment'])) {
-      $item['link_path'] .= '#' . $options['fragment'];
+
+    if (isset($expected_item['parents'])) {
+      $parent_ids = array_values($menu_link_manager->getParentIds($menu_plugin_id));
+      $this->assertEqual($expected_item['parents'], $parent_ids);
+      unset($expected_item['parents']);
+    }
+
+    if (isset($expected_item['langcode']) && $entity) {
+      $this->assertEqual($entity->langcode->value, $expected_item['langcode']);
+      unset($expected_item['langcode']);
     }
+
     foreach ($expected_item as $key => $value) {
-      $this->assertEqual($item[$key], $value);
+      $this->assertTrue(isset($definition[$key]));
+      $this->assertEqual($definition[$key], $value);
     }
   }
 
diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
index dd47b47..56154d1 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -452,10 +452,10 @@ function hook_node_validate(\Drupal\node\NodeInterface $node, $form, &$form_stat
  * @ingroup entity_crud
  */
 function hook_node_submit(\Drupal\node\NodeInterface $node, $form, &$form_state) {
-  // Decompose the selected menu parent option into 'menu_name' and 'plid', if
+  // Decompose the selected menu parent option into 'menu_name' and 'parent', if
   // the form used the default parent selection widget.
   if (!empty($form_state['values']['menu']['parent'])) {
-    list($node->menu['menu_name'], $node->menu['plid']) = explode(':', $form_state['values']['menu']['parent']);
+    list($node->menu['menu_name'], $node->menu['parent']) = explode(':', $form_state['values']['menu']['parent']);
   }
 }
 
diff --git a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
index 69e4235..288cf4d82 100644
--- a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
+++ b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
@@ -214,7 +214,7 @@ function testInstallConfig() {
    */
   function testEnableModulesFixedList() {
     // Install system module.
-    $this->container->get('module_handler')->install(array('system', 'menu_link'));
+    $this->container->get('module_handler')->install(array('system', 'menu_link_content'));
     $entity_manager = \Drupal::entityManager();
 
     // entity_test is loaded via $modules; its entity type should exist.
diff --git a/core/modules/system/config/install/system.menu.yml b/core/modules/system/config/install/system.menu.yml
deleted file mode 100644
index e73636c..0000000
--- a/core/modules/system/config/install/system.menu.yml
+++ /dev/null
@@ -1 +0,0 @@
-active_menus_default: []
diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml
index 5375524..2e9219d 100644
--- a/core/modules/system/config/schema/system.schema.yml
+++ b/core/modules/system/config/schema/system.schema.yml
@@ -168,17 +168,6 @@ system.logging:
       type: string
       label: 'Error messages to display'
 
-system.menu:
-  type: mapping
-  label: 'Menu settings'
-  mapping:
-    active_menus_default:
-      type: sequence
-      label: 'Active menus'
-      sequence:
-        - type: string
-          label: 'Menu'
-
 system.performance:
   type: mapping
   label: 'Performance settings'
diff --git a/core/modules/system/src/Controller/AdminController.php b/core/modules/system/src/Controller/AdminController.php
index 7445d52..d9800f0 100644
--- a/core/modules/system/src/Controller/AdminController.php
+++ b/core/modules/system/src/Controller/AdminController.php
@@ -36,7 +36,7 @@ public function index() {
         // Sort links by title.
         uasort($admin_tasks, array('\Drupal\Component\Utility\SortArray', 'sortByTitleElement'));
         // Move 'Configure permissions' links to the bottom of each section.
-        $permission_key = "user.admin.people.permissions.$module";
+        $permission_key = "user.admin_permissions.$module";
         if (isset($admin_tasks[$permission_key])) {
           $permission_task = $admin_tasks[$permission_key];
           unset($admin_tasks[$permission_key]);
diff --git a/core/modules/system/src/Controller/SystemController.php b/core/modules/system/src/Controller/SystemController.php
index b960968..c811e85 100644
--- a/core/modules/system/src/Controller/SystemController.php
+++ b/core/modules/system/src/Controller/SystemController.php
@@ -12,6 +12,8 @@
 use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Core\Extension\ThemeHandlerInterface;
 use Drupal\Core\Form\FormBuilderInterface;
+use Drupal\Core\Menu\MenuLinkTreeInterface;
+use Drupal\Core\Menu\MenuTreeParameters;
 use Drupal\Core\Theme\ThemeAccessCheck;
 use Drupal\system\SystemManager;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -57,6 +59,13 @@ class SystemController extends ControllerBase {
   protected $themeHandler;
 
   /**
+   * The menu link tree service.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkTreeInterface
+   */
+  protected $menuLinkTree;
+
+  /**
    * Constructs a new SystemController.
    *
    * @param \Drupal\system\SystemManager $systemManager
@@ -69,13 +78,16 @@ class SystemController extends ControllerBase {
    *   The form builder.
    * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
    *   The theme handler.
+   * @param \Drupal\Core\Menu\MenuLinkTreeInterface
+   *   The menu link tree service.
    */
-  public function __construct(SystemManager $systemManager, QueryFactory $queryFactory, ThemeAccessCheck $theme_access, FormBuilderInterface $form_builder, ThemeHandlerInterface $theme_handler) {
+  public function __construct(SystemManager $systemManager, QueryFactory $queryFactory, ThemeAccessCheck $theme_access, FormBuilderInterface $form_builder, ThemeHandlerInterface $theme_handler, MenuLinkTreeInterface $menu_link_tree) {
     $this->systemManager = $systemManager;
     $this->queryFactory = $queryFactory;
     $this->themeAccess = $theme_access;
     $this->formBuilder = $form_builder;
     $this->themeHandler = $theme_handler;
+    $this->menuLinkTree = $menu_link_tree;
   }
 
   /**
@@ -87,66 +99,49 @@ public static function create(ContainerInterface $container) {
       $container->get('entity.query'),
       $container->get('access_check.theme'),
       $container->get('form_builder'),
-      $container->get('theme_handler')
+      $container->get('theme_handler'),
+      $container->get('menu.link_tree')
     );
   }
 
   /**
    * Provide the administration overview page.
    *
-   * @param string $path
-   *   The administrative path for which to display child links.
+   * @param string $link_id
+   *   The ID of the administrative path link for which to display child links.
    *
    * @return array
    *   A renderable array of the administration overview page.
    */
-  public function overview($path) {
+  public function overview($link_id) {
     // Check for status report errors.
     if ($this->systemManager->checkRequirements() && $this->currentUser()->hasPermission('administer site configuration')) {
       drupal_set_message($this->t('One or more problems were detected with your Drupal installation. Check the <a href="@status">status report</a> for more information.', array('@status' => url('admin/reports/status'))), 'error');
     }
+    // Load all menu links below it.
+    $parameters = new MenuTreeParameters();
+    $parameters->setRoot($link_id)->excludeRoot()->setTopLevelOnly()->excludeHiddenLinks();
+    $tree = $this->menuLinkTree->load(NULL, $parameters);
+    $manipulators = array(
+      array('callable' => 'menu.default_tree_manipulators:checkAccess'),
+      array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
+    );
+    $tree = $this->menuLinkTree->transform($tree, $manipulators);
     $blocks = array();
-    // Load all links on $path and menu links below it.
-    $query = $this->queryFactory->get('menu_link')
-      ->condition('link_path', $path)
-      ->condition('module', 'system');
-    $result = $query->execute();
-    $menu_link_storage = $this->entityManager()->getStorage('menu_link');
-    if ($system_link = $menu_link_storage->loadMultiple($result)) {
-      $system_link = reset($system_link);
-      $query = $this->queryFactory->get('menu_link')
-        ->condition('link_path', 'admin/help', '<>')
-        ->condition('menu_name', $system_link->menu_name)
-        ->condition('plid', $system_link->id())
-        ->condition('hidden', 0);
-      $result = $query->execute();
-      if (!empty($result)) {
-        $menu_links = $menu_link_storage->loadMultiple($result);
-        foreach ($menu_links as $item) {
-          _menu_link_translate($item);
-          if (!$item['access']) {
-            continue;
-          }
-          // The link description, either derived from 'description' in hook_menu()
-          // or customized via Menu UI module is used as title attribute.
-          if (!empty($item['localized_options']['attributes']['title'])) {
-            $item['description'] = $item['localized_options']['attributes']['title'];
-            unset($item['localized_options']['attributes']['title']);
-          }
-          $block = $item;
-          $block['content'] = array(
-            '#theme' => 'admin_block_content',
-            '#content' => $this->systemManager->getAdminBlock($item),
-          );
+    foreach ($tree as $key => $element) {
+      $link = $element->link;
+      $block['title'] = $link->getTitle();
+      $block['description'] = $link->getDescription();
+      $block['content'] = array(
+        '#theme' => 'admin_block_content',
+        '#content' => $this->systemManager->getAdminBlock($link),
+      );
 
-          if (!empty($block['content']['#content'])) {
-            // Prepare for sorting as in function _menu_tree_check_access().
-            // The weight is offset so it is always positive, with a uniform 5-digits.
-            $blocks[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $block;
-          }
-        }
+      if (!empty($block['content']['#content'])) {
+        $blocks[$key] = $block;
       }
     }
+
     if ($blocks) {
       ksort($blocks);
       return array(
diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php
index da1ecd0..53dcd57 100644
--- a/core/modules/system/src/Form/ModulesListForm.php
+++ b/core/modules/system/src/Form/ModulesListForm.php
@@ -9,18 +9,20 @@
 
 use Drupal\Component\Utility\String;
 use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Controller\TitleResolverInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
-use Drupal\Core\Entity\Query\QueryFactory;
-use Drupal\Core\Entity\Query\QueryFactoryInterface;
 use Drupal\Core\Extension\Extension;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
+use Drupal\Core\Menu\MenuLinkManagerInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\Core\Access\AccessManager;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Provides module installation interface.
@@ -61,11 +63,18 @@ class ModulesListForm extends FormBase {
   protected $entityManager;
 
   /**
-   * The query factory.
+   * The title resolver.
    *
-   * @var \Drupal\Core\Entity\Query\QueryFactory
+   * @var \Drupal\Core\Controller\TitleResolverInterface
    */
-  protected $queryFactory;
+  protected $titleResolver;
+
+  /**
+   * The route provider.
+   *
+   * @var \Drupal\Core\Routing\RouteProviderInterface
+   */
+  protected $routeProvider;
 
   /**
    * The current route match.
@@ -75,6 +84,13 @@ class ModulesListForm extends FormBase {
   protected $routeMatch;
 
   /**
+   * The menu link manager.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkManagerInterface
+   */
+  protected $menuLinkManager;
+
+  /**
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
@@ -83,9 +99,11 @@ public static function create(ContainerInterface $container) {
       $container->get('keyvalue.expirable')->get('module_list'),
       $container->get('access_manager'),
       $container->get('entity.manager'),
-      $container->get('entity.query'),
       $container->get('current_user'),
-      $container->get('current_route_match')
+      $container->get('current_route_match'),
+      $container->get('title_resolver'),
+      $container->get('router.route_provider'),
+      $container->get('plugin.manager.menu.link')
     );
   }
 
@@ -100,21 +118,27 @@ public static function create(ContainerInterface $container) {
    *   Access manager.
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
-   * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
-   *   The entity query factory.
    * @param \Drupal\Core\Session\AccountInterface $current_user
    *   The current user.
    * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
    *   The current route match.
+   * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
+   *   The title resolver.
+   * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
+   *   The route provider.
+   * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager
+   *   The menu link manager.
    */
-  public function __construct(ModuleHandlerInterface $module_handler, KeyValueStoreExpirableInterface $key_value_expirable, AccessManager $access_manager, EntityManagerInterface $entity_manager, QueryFactory $query_factory, AccountInterface $current_user, RouteMatchInterface $route_match) {
+  public function __construct(ModuleHandlerInterface $module_handler, KeyValueStoreExpirableInterface $key_value_expirable, AccessManager $access_manager, EntityManagerInterface $entity_manager, AccountInterface $current_user,  RouteMatchInterface $route_match, TitleResolverInterface $title_resolver, RouteProviderInterface $route_provider, MenuLinkManagerInterface $menu_link_manager) {
     $this->moduleHandler = $module_handler;
     $this->keyValueExpirable = $key_value_expirable;
     $this->accessManager = $access_manager;
     $this->entityManager = $entity_manager;
-    $this->queryFactory = $query_factory;
     $this->currentUser = $current_user;
     $this->routeMatch = $route_match;
+    $this->titleResolver = $title_resolver;
+    $this->routeProvider = $route_provider;
+    $this->menuLinkManager = $menu_link_manager;
   }
 
   /**
@@ -249,11 +273,23 @@ protected function buildRow(array $modules, Extension $module, $distribution) {
     if ($module->status && isset($module->info['configure'])) {
       $route_parameters = isset($module->info['configure_parameters']) ? $module->info['configure_parameters'] : array();
       if ($this->accessManager->checkNamedRoute($module->info['configure'], $route_parameters, $this->currentUser)) {
-        $result = $this->queryFactory->get('menu_link')
-          ->condition('route_name', $module->info['configure'])
-          ->execute();
-        $menu_items = $this->entityManager->getStorage('menu_link')->loadMultiple($result);
-        $item = reset($menu_items);
+
+        $links = $this->menuLinkManager->loadLinksByRoute($module->info['configure']);
+        /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
+        $link = reset($links);
+        // Most configure links have a corresponding menu link, though some just
+        // have a route.
+        if ($link) {
+          $description = $link->getDescription();
+        }
+        else {
+          $request = new Request();
+          $request->attributes->set('_route_name', $module->info['configure']);
+          $route_object = $this->routeProvider->getRouteByName($module->info['configure']);
+          $request->attributes->set('_route', $route_object);
+          $description = $this->titleResolver->getTitle($request, $route_object);
+        }
+
         $row['links']['configure'] = array(
           '#type' => 'link',
           '#title' => $this->t('Configure'),
@@ -262,7 +298,7 @@ protected function buildRow(array $modules, Extension $module, $distribution) {
           '#options' => array(
             'attributes' => array(
               'class' => array('module-link', 'module-link-configure'),
-              'title' => $item['description'],
+              'title' => $description,
             ),
           ),
         );
diff --git a/core/modules/system/src/Plugin/Block/SystemMenuBlock.php b/core/modules/system/src/Plugin/Block/SystemMenuBlock.php
index 5381471..258ffbb 100644
--- a/core/modules/system/src/Plugin/Block/SystemMenuBlock.php
+++ b/core/modules/system/src/Plugin/Block/SystemMenuBlock.php
@@ -10,7 +10,8 @@
 use Drupal\Component\Utility\NestedArray;
 use Drupal\block\BlockBase;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\menu_link\MenuTreeInterface;
+use Drupal\Core\Menu\MenuActiveTrailInterface;
+use Drupal\Core\Menu\MenuLinkTreeInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 
@@ -27,13 +28,20 @@
 class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterface {
 
   /**
-   * The menu tree.
+   * The menu link tree service.
    *
-   * @var \Drupal\menu_link\MenuTreeInterface
+   * @var \Drupal\Core\Menu\MenuLinkTreeInterface
    */
   protected $menuTree;
 
   /**
+   * The active menu trail service.
+   *
+   * @var \Drupal\Core\Menu\MenuActiveTrailInterface
+   */
+  protected $menuActiveTrail;
+
+  /**
    * Constructs a new SystemMenuBlock.
    *
    * @param array $configuration
@@ -42,12 +50,15 @@ class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterfa
    *   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.
+   * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
+   *   The menu tree service.
+   * @param \Drupal\Core\Menu\MenuActiveTrailInterface $menu_active_trail
+   *   The active menu trail service.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuTreeInterface $menu_tree) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuLinkTreeInterface $menu_tree, MenuActiveTrailInterface $menu_active_trail) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->menuTree = $menu_tree;
+    $this->menuActiveTrail = $menu_active_trail;
   }
 
   /**
@@ -58,7 +69,8 @@ public static function create(ContainerInterface $container, array $configuratio
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $container->get('menu_link.tree')
+      $container->get('menu.link_tree'),
+      $container->get('menu.active_trail')
     );
   }
 
@@ -66,8 +78,15 @@ public static function create(ContainerInterface $container, array $configuratio
    * {@inheritdoc}
    */
   public function build() {
-    $menu = $this->getDerivativeId();
-    return $this->menuTree->renderMenu($menu);
+    $menu_name = $this->getDerivativeId();
+    $parameters = $this->menuTree->getCurrentRouteMenuTreeParameters($menu_name);
+    $tree = $this->menuTree->load($menu_name, $parameters);
+    $manipulators = array(
+      array('callable' => 'menu.default_tree_manipulators:checkAccess'),
+      array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
+    );
+    $tree = $this->menuTree->transform($tree, $manipulators);
+    return $this->menuTree->build($tree);
   }
 
   /**
@@ -91,9 +110,7 @@ public function defaultConfiguration() {
   public function getCacheKeys() {
     // Add a key for the active menu trail.
     $menu = $this->getDerivativeId();
-    $active_trail = $this->menuTree->getActiveTrailIds($menu);
-    $active_trail_key = 'trail.' . implode('|', $active_trail);
-    return array_merge(parent::getCacheKeys(), array($active_trail_key));
+    return array_merge(parent::getCacheKeys(), array($this->menuActiveTrail->getActiveTrailCacheKey($menu)));
   }
 
   /**
@@ -114,7 +131,7 @@ public function getCacheTags() {
   protected function getRequiredCacheContexts() {
     // Menu blocks must be cached per role: different roles may have access to
     // different menu links.
-    return array('cache_context.user.roles');
+    return array('cache_context.user.roles', 'cache_context.language');
   }
 
 }
diff --git a/core/modules/system/src/SystemManager.php b/core/modules/system/src/SystemManager.php
index 696efc1..00f97aa 100644
--- a/core/modules/system/src/SystemManager.php
+++ b/core/modules/system/src/SystemManager.php
@@ -6,8 +6,11 @@
 
 namespace Drupal\system;
 
-use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Menu\MenuActiveTrailInterface;
+use Drupal\Core\Menu\MenuLinkTreeInterface;
+use Drupal\Core\Menu\MenuLinkInterface;
+use Drupal\Core\Menu\MenuTreeParameters;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Extension\ModuleHandlerInterface;
@@ -33,18 +36,25 @@ class SystemManager {
   protected $database;
 
   /**
-   * The menu link storage.
+   * The request stack.
    *
-   * @var \Drupal\menu_link\MenuLinkStorageInterface
+   * @var \Symfony\Component\HttpFoundation\RequestStack
    */
-  protected $menuLinkStorage;
+  protected $requestStack;
 
   /**
-   * The request stack.
+   * The menu link tree manager.
    *
-   * @var \Symfony\Component\HttpFoundation\RequestStack
+   * @var \Drupal\Core\Menu\MenuLinkTreeInterface
    */
-  protected $requestStack;
+  protected $menuTree;
+
+  /**
+   * The active menu trail service.
+   *
+   * @var \Drupal\Core\Menu\MenuActiveTrailInterface
+   */
+  protected $menuActiveTrail;
 
   /**
    * A static cache of menu items.
@@ -79,12 +89,17 @@ class SystemManager {
    *   The entity manager.
    * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
    *   The request stack.
+   * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
+   *   The menu tree manager.
+   * @param \Drupal\Core\Menu\MenuActiveTrailInterface $menu_active_trail
+   *   The active menu trail service.
    */
-  public function __construct(ModuleHandlerInterface $module_handler, Connection $database, EntityManagerInterface $entity_manager, RequestStack $request_stack) {
+  public function __construct(ModuleHandlerInterface $module_handler, Connection $database, EntityManagerInterface $entity_manager, RequestStack $request_stack, MenuLinkTreeInterface $menu_tree, MenuActiveTrailInterface $menu_active_trail) {
     $this->moduleHandler = $module_handler;
     $this->database = $database;
-    $this->menuLinkStorage = $entity_manager->getStorage('menu_link');
     $this->requestStack = $request_stack;
+    $this->menuTree = $menu_tree;
+    $this->menuActiveTrail = $menu_active_trail;
   }
 
   /**
@@ -171,11 +186,10 @@ public function getMaxSeverity(&$requirements) {
    *   A render array suitable for drupal_render.
    */
   public function getBlockContents() {
-    $request = $this->requestStack->getCurrentRequest();
-    $route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME);
-    $items = $this->menuLinkStorage->loadByProperties(array('route_name' => $route_name));
-    $item = reset($items);
-    if ($content = $this->getAdminBlock($item)) {
+    // We hard-code the menu name here since otherwise a link in the tools menu
+    // or elsewhere could give us a blank block.
+    $link = $this->menuActiveTrail->getActiveLink('admin');
+    if ($link && $content = $this->getAdminBlock($link)) {
       $output = array(
         '#theme' => 'admin_block_content',
         '#content' => $content,
@@ -192,48 +206,33 @@ public function getBlockContents() {
   /**
    * Provide a single block on the administration overview page.
    *
-   * @param \Drupal\menu_link\MenuLinkInterface|array $item
+   * @param \Drupal\Core\Menu\MenuLinkInterface $instance
    *   The menu item to be displayed.
    *
    * @return array
    *   An array of menu items, as expected by theme_admin_block_content().
    */
-  public function getAdminBlock($item) {
-    if (!isset($item['mlid'])) {
-      $menu_links = $this->menuLinkStorage->loadByProperties(array('link_path' => $item['path'], 'module' => 'system'));
-      if ($menu_links) {
-        $menu_link = reset($menu_links);
-        $item['mlid'] = $menu_link->id();
-        $item['menu_name'] = $menu_link->menu_name;
-      }
-      else {
-        return array();
-      }
-    }
-
-    if (isset($this->menuItems[$item['mlid']])) {
-      return $this->menuItems[$item['mlid']];
-    }
-
+  public function getAdminBlock(MenuLinkInterface $instance) {
     $content = array();
-    $menu_links = $this->menuLinkStorage->loadByProperties(array('plid' => $item['mlid'], 'menu_name' => $item['menu_name'], 'hidden' => 0));
-    foreach ($menu_links as $link) {
-      _menu_link_translate($link);
-      if ($link['access']) {
-        // The link description, either derived from 'description' in
-        // hook_menu() or customized via Menu UI module is used as title attribute.
-        if (!empty($link['localized_options']['attributes']['title'])) {
-          $link['description'] = $link['localized_options']['attributes']['title'];
-          unset($link['localized_options']['attributes']['title']);
-        }
-        // Prepare for sorting as in function _menu_tree_check_access().
-        // The weight is offset so it is always positive, with a uniform 5-digits.
-        $key = (50000 + $link['weight']) . ' ' . Unicode::strtolower($link['title']) . ' ' . $link['mlid'];
-        $content[$key] = $link;
-      }
+    // Only find the children of this link.
+    $link_id = $instance->getPluginId();
+    $parameters = new MenuTreeParameters();
+    $parameters->setRoot($link_id)->excludeRoot()->setTopLevelOnly()->excludeHiddenLinks();
+    $tree = $this->menuTree->load(NULL, $parameters);
+    $manipulators = array(
+      array('callable' => 'menu.default_tree_manipulators:checkAccess'),
+      array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
+    );
+    $tree = $this->menuTree->transform($tree, $manipulators);
+    foreach ($tree as $key => $element) {
+      /** @var $link \Drupal\Core\Menu\MenuLinkInterface */
+      $link = $element->link;
+      $content[$key]['title'] = $link->getTitle();
+      $content[$key]['options'] = $link->getOptions();
+      $content[$key]['description'] = $link->getDescription();
+      $content[$key]['url'] = $link->getUrlObject();
     }
     ksort($content);
-    $this->menuItems[$item['mlid']] = $content;
     return $content;
   }
 
diff --git a/core/modules/system/src/Tests/Menu/BreadcrumbTest.php b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php
index f1bf640..ec6201a 100644
--- a/core/modules/system/src/Tests/Menu/BreadcrumbTest.php
+++ b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php
@@ -83,15 +83,10 @@ function testBreadCrumbs() {
     );
     $this->assertBreadcrumb('admin/structure/menu/manage/tools', $trail);
 
-    $mlid_node_add = \Drupal::entityQuery('menu_link')
-      ->condition('machine_name', 'node.add_page')
-      ->condition('module', 'node')
-      ->execute();
-    $mlid_node_add = reset($mlid_node_add);
     $trail += array(
       'admin/structure/menu/manage/tools' => t('Tools'),
     );
-    $this->assertBreadcrumb("admin/structure/menu/item/$mlid_node_add/edit", $trail);
+    $this->assertBreadcrumb("admin/structure/menu/link/node.add_page/edit", $trail);
     $this->assertBreadcrumb('admin/structure/menu/manage/tools/add', $trail);
 
     // Verify Node administration breadcrumbs.
@@ -165,7 +160,7 @@ function testBreadCrumbs() {
     // Alter node type menu settings.
     \Drupal::config("menu.entity.node.$type")
       ->set('available_menus', $menus)
-      ->set('parent', 'tools:0')
+      ->set('parent', 'tools:')
       ->save();
 
     foreach ($menus as $menu) {
@@ -174,13 +169,13 @@ function testBreadCrumbs() {
       $node2 = $this->drupalCreateNode(array(
         'type' => $type,
         'title' => $title,
-        'menu' => entity_create('menu_link', array(
-          'enabled' => 1,
-          'link_title' => 'Parent ' . $title,
+        'menu' => array(
+          'hidden' => 0,
+          'title' => 'Parent ' . $title,
           'description' => '',
           'menu_name' => $menu,
-          'plid' => 0,
-        )),
+          'parent' => '',
+        ),
       ));
 
       if ($menu == 'tools') {
@@ -192,26 +187,26 @@ function testBreadCrumbs() {
     // link below it, and verify a full breadcrumb for the last child node.
     $menu = 'tools';
     $edit = array(
-      'link_title' => 'Root',
-      'link_path' => 'node',
+      'title[0][value]' => 'Root',
+      'url' => 'node',
     );
     $this->drupalPostForm("admin/structure/menu/manage/$menu/add", $edit, t('Save'));
-    $menu_links = entity_load_multiple_by_properties('menu_link', array('link_title' => 'Root'));
+    $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => 'Root'));
     $link = reset($menu_links);
 
     $edit = array(
-      'menu[parent]' => $link['menu_name'] . ':' . $link['mlid'],
+      'menu[menu_parent]' => $link->getMenuName() . ':' . $link->getPluginId(),
     );
     $this->drupalPostForm('node/' . $parent->id() . '/edit', $edit, t('Save and keep published'));
     $expected = array(
-      "node" => $link['link_title'],
+      "node" => $link->getTitle(),
     );
     $trail = $home + $expected;
     $tree = $expected + array(
-      'node/' . $parent->id() => $parent->menu['link_title'],
+      'node/' . $parent->id() => $parent->menu['title'],
     );
     $trail += array(
-      'node/' . $parent->id() => $parent->menu['link_title'],
+      'node/' . $parent->id() => $parent->menu['title'],
     );
 
     // Add a taxonomy term/tag to last node, and add a link for that term to the
@@ -241,32 +236,36 @@ function testBreadCrumbs() {
       }
       $parent_tid = $term->id();
     }
-    $parent_mlid = 0;
+    $parent_mlid = '';
     foreach ($tags as $name => $data) {
       $term = $data['term'];
       $edit = array(
-        'link_title' => "$name link",
-        'link_path' => "taxonomy/term/{$term->id()}",
-        'parent' => "$menu:{$parent_mlid}",
+        'title[0][value]' => "$name link",
+        'url' => "taxonomy/term/{$term->id()}",
+        'menu_parent' => "$menu:{$parent_mlid}",
       );
       $this->drupalPostForm("admin/structure/menu/manage/$menu/add", $edit, t('Save'));
-      $menu_links = entity_load_multiple_by_properties('menu_link', array('link_title' => $edit['link_title'], 'link_path' => $edit['link_path']));
+      $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $edit['title[0][value]'], 'route_name' => 'taxonomy.term_page', 'route_parameters' => serialize(array('taxonomy_term' => $term->id()))));
       $tags[$name]['link'] = reset($menu_links);
-      $tags[$name]['link']['link_path'] = $edit['link_path'];
-      $parent_mlid = $tags[$name]['link']['mlid'];
+      $parent_mlid = $tags[$name]['link']->getPluginId();
     }
 
     // Verify expected breadcrumbs for menu links.
     $trail = $home;
     $tree = array();
+    // Logout the user because we want to check the active class as well, which
+    // is just rendered as anonymous user.
+    $this->drupalLogout();
     foreach ($tags as $name => $data) {
       $term = $data['term'];
+      /** @var \Drupal\menu_link_content\Entity\MenuLinkContentInterface $link */
       $link = $data['link'];
 
+      $link_path = $link->getUrlObject()->getInternalPath();
       $tree += array(
-        $link['link_path'] => $link['link_title'],
+        $link_path => $link->getTitle(),
       );
-      $this->assertBreadcrumb($link['link_path'], $trail, $term->getName(), $tree);
+      $this->assertBreadcrumb($link_path, $trail, $term->getName(), $tree);
       $this->assertRaw(String::checkPlain($parent->getTitle()), 'Tagged node found.');
 
       // Additionally make sure that this link appears only once; i.e., the
@@ -275,14 +274,14 @@ function testBreadCrumbs() {
       // other than the breadcrumb trail.
       $elements = $this->xpath('//div[@id=:menu]/descendant::a[@href=:href]', array(
         ':menu' => 'block-bartik-tools',
-        ':href' => url($link['link_path']),
+        ':href' => url($link_path),
       ));
-      $this->assertTrue(count($elements) == 1, "Link to {$link['link_path']} appears only once.");
+      $this->assertTrue(count($elements) == 1, "Link to {$link_path} appears only once.");
 
       // Next iteration should expect this tag as parent link.
       // Note: Term name, not link name, due to taxonomy_term_page().
       $trail += array(
-        $link['link_path'] => $term->getName(),
+        $link_path => $term->getName(),
       );
     }
 
@@ -292,7 +291,6 @@ function testBreadCrumbs() {
     user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array(
       'access user profiles',
     ));
-    $this->drupalLogout();
 
     // Verify breadcrumb on front page.
     $this->assertBreadcrumb('<front>', array());
@@ -359,4 +357,5 @@ function testBreadCrumbs() {
     $this->assertBreadcrumb('admin/reports/dblog', $trail, t('Recent log messages'));
     $this->assertNoResponse(403);
   }
+
 }
diff --git a/core/modules/system/src/Tests/Menu/LinksTest.php b/core/modules/system/src/Tests/Menu/LinksTest.php
index eecfb8c..552b1ae 100644
--- a/core/modules/system/src/Tests/Menu/LinksTest.php
+++ b/core/modules/system/src/Tests/Menu/LinksTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Menu;
 
+use Drupal\Component\Utility\String;
 use Drupal\locale\TranslationString;
 use Drupal\simpletest\WebTestBase;
 
@@ -14,6 +15,9 @@
  * Tests handling of menu links hierarchies.
  *
  * @group Menu
+ *
+ * @todo Move this under menu_link_content module.
+ *   https://www.drupal.org/node/2310353
  */
 class LinksTest extends WebTestBase {
 
@@ -22,7 +26,14 @@ class LinksTest extends WebTestBase {
    *
    * @var array
    */
-  public static $modules = array('router_test');
+  public static $modules = array('router_test', 'menu_link_content');
+
+  /**
+   * The menu link plugin manager.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkManagerInterface $menuLinkManager
+   */
+  protected $menuLinkManager;
 
   /**
    * {@inheritdoc}
@@ -30,6 +41,8 @@ class LinksTest extends WebTestBase {
   public function setUp() {
     parent::setUp();
 
+    $this->menuLinkManager = \Drupal::service('plugin.manager.menu.link');
+
     entity_create('menu', array(
       'id' => 'menu_test',
       'label' => 'Test menu',
@@ -41,55 +54,60 @@ public function setUp() {
    * Create a simple hierarchy of links.
    */
   function createLinkHierarchy($module = 'menu_test') {
-    // First remove all the menu links.
-    $menu_links = menu_link_load_multiple();
-    menu_link_delete_multiple(array_keys($menu_links), TRUE, TRUE);
+    // First remove all the menu links in the menu.
+    $this->menuLinkManager->deleteLinksInMenu('menu_test');
 
     // Then create a simple link hierarchy:
-    // - $parent
-    //   - $child-1
-    //      - $child-1-1
-    //      - $child-1-2
-    //   - $child-2
+    // - parent
+    //   - child-1
+    //      - child-1-1
+    //      - child-1-2
+    //   - child-2
     $base_options = array(
-      'link_title' => 'Menu link test',
-      'module' => $module,
+      'title' => 'Menu link test',
+      'provider' => $module,
       'menu_name' => 'menu_test',
+      'bundle' => 'menu_link_content'
     );
 
-    $links['parent'] = $base_options + array(
-      'link_path' => 'menu-test/parent',
+    $parent = $base_options + array(
+      'route_name' => 'menu_test.hierarchy_parent',
     );
-    $links['parent'] = entity_create('menu_link', $links['parent']);
-    $links['parent']->save();
+    $link = entity_create('menu_link_content', $parent);
+    $link->save();
+    $links['parent'] = $link->getPluginId();
 
-    $links['child-1'] = $base_options + array(
-      'link_path' => 'menu-test/parent/child-1',
-      'plid' => $links['parent']['mlid'],
+    $child_1 = $base_options + array(
+      'route_name' => 'menu_test.hierarchy_parent_child',
+      'parent' => $links['parent'],
     );
-    $links['child-1'] = entity_create('menu_link', $links['child-1']);
-    $links['child-1']->save();
+    $link = entity_create('menu_link_content', $child_1);
+    $link->save();
+    $links['child-1'] = $link->getPluginId();
 
-    $links['child-1-1'] = $base_options + array(
-      'link_path' => 'menu-test/parent/child-1/child-1-1',
-      'plid' => $links['child-1']['mlid'],
+    $child_1_1 = $base_options + array(
+      'route_name' => 'menu_test.hierarchy_parent_child2',
+      'parent' => $links['child-1'],
     );
-    $links['child-1-1'] = entity_create('menu_link', $links['child-1-1']);
-    $links['child-1-1']->save();
+    $link = entity_create('menu_link_content', $child_1_1);
+    $link->save();
+    $links['child-1-1'] = $link->getPluginId();
 
-    $links['child-1-2'] = $base_options + array(
-      'link_path' => 'menu-test/parent/child-1/child-1-2',
-      'plid' => $links['child-1']['mlid'],
+    $child_1_2 = $base_options + array(
+      'route_name' => 'menu_test.hierarchy_parent_child2',
+      'parent' => $links['child-1'],
     );
-    $links['child-1-2'] = entity_create('menu_link', $links['child-1-2']);
-    $links['child-1-2']->save();
+    $link = entity_create('menu_link_content', $child_1_2);
+    $link->save();
+    $links['child-1-2'] = $link->getPluginId();
 
-    $links['child-2'] = $base_options + array(
-      'link_path' => 'menu-test/parent/child-2',
-      'plid' => $links['parent']['mlid'],
+    $child_2 = $base_options + array(
+      'route_name' => 'menu_test.hierarchy_parent_child',
+      'parent' => $links['parent'],
     );
-    $links['child-2'] = entity_create('menu_link', $links['child-2']);
-    $links['child-2']->save();
+    $link = entity_create('menu_link_content', $child_2);
+    $link->save();
+    $links['child-2'] = $link->getPluginId();
 
     return $links;
   }
@@ -98,13 +116,12 @@ function createLinkHierarchy($module = 'menu_test') {
    * Assert that at set of links is properly parented.
    */
   function assertMenuLinkParents($links, $expected_hierarchy) {
-    foreach ($expected_hierarchy as $child => $parent) {
-      $mlid = $links[$child]['mlid'];
-      $plid = $parent ? $links[$parent]['mlid'] : 0;
+    foreach ($expected_hierarchy as $id => $parent) {
+      /* @var \Drupal\Core\Menu\MenuLinkInterface $menu_link_plugin  */
+      $menu_link_plugin = $this->menuLinkManager->createInstance($links[$id]);
+      $expected_parent = isset($links[$parent]) ? $links[$parent] : '';
 
-      $menu_link = menu_link_load($mlid);
-      menu_link_save($menu_link);
-      $this->assertEqual($menu_link['plid'], $plid, format_string('Menu link %mlid has parent of %plid, expected %expected_plid.', array('%mlid' => $mlid, '%plid' => $menu_link['plid'], '%expected_plid' => $plid)));
+      $this->assertEqual($menu_link_plugin->getParent(), $expected_parent, String::format('Menu link %id has parent of %parent, expected %expected_parent.', array('%id' => $id, '%parent' => $menu_link_plugin->getParent(), '%expected_parent' => $expected_parent)));
     }
   }
 
@@ -116,7 +133,7 @@ function testMenuLinkReparenting($module = 'menu_test') {
     $links = $this->createLinkHierarchy($module);
 
     $expected_hierarchy = array(
-      'parent' => FALSE,
+      'parent' => '',
       'child-1' => 'parent',
       'child-1-1' => 'child-1',
       'child-1-2' => 'child-1',
@@ -127,11 +144,15 @@ function testMenuLinkReparenting($module = 'menu_test') {
     // Start over, and move child-1 under child-2, and check that all the
     // childs of child-1 have been moved too.
     $links = $this->createLinkHierarchy($module);
-    $links['child-1']['plid'] = $links['child-2']['mlid'];
-    menu_link_save($links['child-1']);
+    /* @var \Drupal\Core\Menu\MenuLinkInterface $menu_link_plugin  */
+    $this->menuLinkManager->updateDefinition($links['child-1'], array('parent' => $links['child-2']));
+    // Verify that the entity was updated too.
+    $menu_link_plugin = $this->menuLinkManager->createInstance($links['child-1']);
+    $entity = \Drupal::entityManager()->loadEntityByUuid('menu_link_content', $menu_link_plugin->getDerivativeId());
+    $this->assertEqual($entity->getParentId(), $links['child-2']);
 
     $expected_hierarchy = array(
-      'parent' => FALSE,
+      'parent' => '',
       'child-1' => 'child-2',
       'child-1-1' => 'child-1',
       'child-1-2' => 'child-1',
@@ -140,29 +161,9 @@ function testMenuLinkReparenting($module = 'menu_test') {
     $this->assertMenuLinkParents($links, $expected_hierarchy);
 
     // Start over, and delete child-1, and check that the children of child-1
-    // have been reassigned to the parent. menu_link_delete() will cowardly
-    // refuse to delete a menu link defined by the system module, so skip the
-    // test in that case.
-    if ($module != 'system') {
-      $links = $this->createLinkHierarchy($module);
-      menu_link_delete($links['child-1']['mlid']);
-
-      $expected_hierarchy = array(
-        'parent' => FALSE,
-        'child-1-1' => 'parent',
-        'child-1-2' => 'parent',
-        'child-2' => 'parent',
-      );
-      $this->assertMenuLinkParents($links, $expected_hierarchy);
-    }
-
-    // Start over, forcefully delete child-1 from the database, simulating a
-    // database crash. Check that the children of child-1 have been reassigned
-    // to the parent, going up on the old path hierarchy stored in each of the
-    // links.
+    // have been reassigned to the parent.
     $links = $this->createLinkHierarchy($module);
-    // Don't do that at home.
-    entity_delete_multiple('menu_link', array($links['child-1']['mlid']));
+    $this->menuLinkManager->removeDefinition($links['child-1']);
 
     $expected_hierarchy = array(
       'parent' => FALSE,
@@ -172,119 +173,26 @@ function testMenuLinkReparenting($module = 'menu_test') {
     );
     $this->assertMenuLinkParents($links, $expected_hierarchy);
 
-    // Start over, forcefully delete the parent from the database, simulating a
-    // database crash. Check that the children of parent are now top-level.
-    $links = $this->createLinkHierarchy($module);
-    // Don't do that at home.
-    db_delete('menu_links')
-      ->condition('mlid', $links['parent']['mlid'])
-      ->execute();
-
-    $expected_hierarchy = array(
-      'child-1-1' => 'child-1',
-      'child-1-2' => 'child-1',
-      'child-2' => FALSE,
-    );
-    $this->assertMenuLinkParents($links, $expected_hierarchy);
-  }
-
-  /**
-   * Tests automatic reparenting.
-   *
-   * Runs tests on menu links defined by the menu_link.static service.
-   */
-  function testMenuLinkRouterReparenting() {
-    // Run all the standard parenting tests on menu links derived from
-    // menu routers.
-    $this->testMenuLinkReparenting('system');
-
-    // Additionnaly, test reparenting based on path.
-    $links = $this->createLinkHierarchy('system');
-
-    // Move child-1-2 has a child of child-2, making the link hierarchy
-    // inconsistent with the path hierarchy.
-    $links['child-1-2']['plid'] = $links['child-2']['mlid'];
-    menu_link_save($links['child-1-2']);
-
-    // Check the new hierarchy.
-    $expected_hierarchy = array(
-      'parent' => FALSE,
-      'child-1' => 'parent',
-      'child-1-1' => 'child-1',
-      'child-2' => 'parent',
-      'child-1-2' => 'child-2',
-    );
-    $this->assertMenuLinkParents($links, $expected_hierarchy);
-
-    // Now delete 'parent' directly from the database, simulating a database
-    // crash. 'child-1' and 'child-2' should get moved to the
-    // top-level.
-    // Don't do that at home.
-    db_delete('menu_links')
-      ->condition('mlid', $links['parent']['mlid'])
-      ->execute();
-    $expected_hierarchy = array(
-      'child-1' => FALSE,
-      'child-1-1' => 'child-1',
-      'child-2' => FALSE,
-      'child-1-2' => 'child-2',
-    );
-    $this->assertMenuLinkParents($links, $expected_hierarchy);
-
-    // Now delete 'child-2' directly from the database, simulating a database
-    // crash. 'child-1-2' will get reparented to the top.
-    // Don't do that at home.
-    db_delete('menu_links')
-      ->condition('mlid', $links['child-2']['mlid'])
-      ->execute();
-    $expected_hierarchy = array(
-      'child-1' => FALSE,
-      'child-1-1' => 'child-1',
-      'child-1-2' => FALSE,
-    );
-    $this->assertMenuLinkParents($links, $expected_hierarchy);
-  }
-
-  /**
-   * Tests the router system integration (route_name and route_parameters).
-   */
-  public function testRouterIntegration() {
-    $menu_link = entity_create('menu_link', array(
-      'link_path' => 'router_test/test1',
-    ));
-    $menu_link->save();
-    $this->assertEqual($menu_link->route_name, 'router_test.1');
-    $this->assertEqual($menu_link->route_parameters, array());
-
-    $menu_link = entity_create('menu_link', array(
-      'link_path' => 'router_test/test3/test',
-    ));
-    $menu_link->save();
-    $this->assertEqual($menu_link->route_name, 'router_test.3');
-    $this->assertEqual($menu_link->route_parameters, array('value' => 'test'));
-
-    $menu_link = entity_load('menu_link', $menu_link->id());
-    $this->assertEqual($menu_link->route_name, 'router_test.3');
-    $this->assertEqual($menu_link->route_parameters, array('value' => 'test'));
+    // @todo Figure out what makes sense to test in terms of automatic
+    //   re-parenting. https://www.drupal.org/node/2309531
   }
 
   /**
-   * Tests uninstall a module providing default links.
+   * Tests uninstalling a module providing default links.
    */
   public function testModuleUninstalledMenuLinks() {
     \Drupal::moduleHandler()->install(array('menu_test'));
     \Drupal::service('router.builder')->rebuild();
-    menu_link_rebuild_defaults();
-    $result = $menu_link = \Drupal::entityQuery('menu_link')->condition('machine_name', 'menu_test')->execute();
-    $menu_links = \Drupal::entityManager()->getStorage('menu_link')->loadMultiple($result);
+    \Drupal::service('plugin.manager.menu.link')->rebuild();
+    $menu_links = $this->menuLinkManager->loadLinksByRoute('menu_test.menu_test');
     $this->assertEqual(count($menu_links), 1);
     $menu_link = reset($menu_links);
-    $this->assertEqual($menu_link->machine_name, 'menu_test');
+    $this->assertEqual($menu_link->getPluginId(), 'menu_test');
 
     // Uninstall the module and ensure the menu link got removed.
     \Drupal::moduleHandler()->uninstall(array('menu_test'));
-    $result = $menu_link = \Drupal::entityQuery('menu_link')->condition('machine_name', 'menu_test')->execute();
-    $menu_links = \Drupal::entityManager()->getStorage('menu_link')->loadMultiple($result);
+    \Drupal::service('plugin.manager.menu.link')->rebuild();
+    $menu_links = $this->menuLinkManager->loadLinksByRoute('menu_test.menu_test');
     $this->assertEqual(count($menu_links), 0);
   }
 
diff --git a/core/modules/system/src/Tests/Menu/MenuLinkDefaultIntegrationTest.php b/core/modules/system/src/Tests/Menu/MenuLinkDefaultIntegrationTest.php
new file mode 100644
index 0000000..6078875
--- /dev/null
+++ b/core/modules/system/src/Tests/Menu/MenuLinkDefaultIntegrationTest.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Menu\MenuLinkDefaultIntegrationTest.
+ */
+
+namespace Drupal\system\Tests\Menu;
+
+use Drupal\Core\Menu\MenuTreeParameters;
+use Drupal\simpletest\KernelTestBase;
+
+/**
+ * Tests integration of static menu links.
+ *
+ * @group Menu
+ */
+class MenuLinkDefaultIntegrationTest extends KernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array(
+    'system',
+    'menu_test',
+  );
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installSchema('system', array('router'));
+  }
+
+  /**
+   * Tests moving a static menu link without a specified menu to the root.
+   */
+  public function testMoveToRoot() {
+    /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
+    $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
+    $menu_link_manager->rebuild();
+
+    $menu_link = $menu_link_manager->getDefinition('menu_test.child');
+    $this->assertEqual($menu_link['parent'], 'menu_test.parent');
+    $this->assertEqual($menu_link['menu_name'], 'test');
+
+    $tree = \Drupal::menuTree()->load('test', new MenuTreeParameters());
+    $this->assertEqual(count($tree), 1);
+    $this->assertEqual($tree['menu_test.parent']->link->getPluginId(), 'menu_test.parent');
+    $this->assertEqual($tree['menu_test.parent']->subtree['menu_test.child']->link->getPluginId(), 'menu_test.child');
+
+    // Ensure that the menu name is not forgotten.
+    $menu_link_manager->updateDefinition('menu_test.child', array('parent' => ''));
+    $menu_link = $menu_link_manager->getDefinition('menu_test.child');
+
+    $this->assertEqual($menu_link['parent'], '');
+    $this->assertEqual($menu_link['menu_name'], 'test');
+
+    $tree = \Drupal::menuTree()->load('test', new MenuTreeParameters());
+    $this->assertEqual(count($tree), 2);
+    $this->assertEqual($tree['menu_test.parent']->link->getPluginId(), 'menu_test.parent');
+    $this->assertEqual($tree['menu_test.child']->link->getPluginId(), 'menu_test.child');
+
+    $this->assertTrue(TRUE);
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Menu/MenuLinkTreeTest.php b/core/modules/system/src/Tests/Menu/MenuLinkTreeTest.php
index 1b97c98..98b9685 100644
--- a/core/modules/system/src/Tests/Menu/MenuLinkTreeTest.php
+++ b/core/modules/system/src/Tests/Menu/MenuLinkTreeTest.php
@@ -43,7 +43,6 @@ class MenuLinkTreeTest extends KernelTestBase {
   public static $modules = array(
     'system',
     'menu_test',
-    'menu_link',
     'menu_link_content',
     'field',
   );
diff --git a/core/modules/system/src/Tests/Menu/MenuRouterRebuildTest.php b/core/modules/system/src/Tests/Menu/MenuRouterRebuildTest.php
deleted file mode 100644
index e56796f..0000000
--- a/core/modules/system/src/Tests/Menu/MenuRouterRebuildTest.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\system\Tests\Menu\MenuRouterRebuildTest.
- */
-
-namespace Drupal\system\Tests\Menu;
-
-use Drupal\simpletest\WebTestBase;
-use Drupal\Core\Language\Language;
-
-/**
- * Tests menu_router_rebuild().
- *
- * @group Menu
- */
-class MenuRouterRebuildTest extends WebTestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = array('language', 'menu_test');
-
-  /**
-   * {@inheritdoc}
-   */
-  function setUp() {
-    parent::setUp();
-
-    $language = new Language(array('id' => 'nl'));
-    language_save($language);
-  }
-
-  /**
-   * Tests configuration context when rebuilding the menu router table.
-   */
-  public function testMenuRouterRebuildContext() {
-    // Enter a language context before rebuilding the menu router tables.
-    \Drupal::languageManager()->setConfigOverrideLanguage(language_load('nl'));
-    \Drupal::service('router.builder')->rebuild();
-
-    // Check that the language context was not used for building the menu item.
-    $menu_items = \Drupal::entityManager()->getStorage('menu_link')->loadByProperties(array('route_name' => 'menu_test.context'));
-    $menu_item = reset($menu_items);
-    $this->assertTrue($menu_item['link_title'] == 'English', 'Config context overrides are ignored when rebuilding menu router items.');
-  }
-
-}
diff --git a/core/modules/system/src/Tests/Menu/MenuRouterTest.php b/core/modules/system/src/Tests/Menu/MenuRouterTest.php
index 26a0beb..270badc 100644
--- a/core/modules/system/src/Tests/Menu/MenuRouterTest.php
+++ b/core/modules/system/src/Tests/Menu/MenuRouterTest.php
@@ -52,11 +52,7 @@ public function testMenuIntegration() {
     $this->doTestMenuOptionalPlaceholders();
     $this->doTestMenuOnRoute();
     $this->doTestMenuName();
-    $this->doTestMenuLinkDefaultsAlter();
-    $this->doTestMenuItemTitlesCases();
-    $this->doTestMenuLinkMaintain();
-    $this->doTestMenuLinkOptions();
-    $this->doTestMenuItemHooks();
+    $this->doTestMenuLinksDiscoveredAlter();
     $this->doTestHookMenuIntegration();
     $this->doTestExoticPath();
   }
@@ -109,182 +105,60 @@ protected function doTestDescriptionMenuItems() {
   }
 
   /**
-   * Tests for menu_link_maintain().
-   */
-  protected function doTestMenuLinkMaintain() {
-    $admin_user = $this->drupalCreateUser(array('administer site configuration'));
-    $this->drupalLogin($admin_user);
-
-    // Create three menu items.
-    menu_link_maintain('menu_test', 'insert', 'menu_test_maintain/1', 'Menu link #1');
-    menu_link_maintain('menu_test', 'insert', 'menu_test_maintain/1', 'Menu link #1-main');
-    menu_link_maintain('menu_test', 'insert', 'menu_test_maintain/2', 'Menu link #2');
-
-    // Move second link to the main-menu, to test caching later on.
-    $menu_links_to_update = entity_load_multiple_by_properties('menu_link', array('link_title' => 'Menu link #1-main', 'customized' => 0, 'module' => 'menu_test'));
-    foreach ($menu_links_to_update as $menu_link) {
-      $menu_link->menu_name = 'main';
-      $menu_link->save();
-    }
-
-    // Load front page.
-    $this->drupalGet('');
-    $this->assertLink('Menu link #1');
-    $this->assertLink('Menu link #1-main');
-    $this->assertLink('Menu link #2');
-
-    // Rename all links for the given path.
-    menu_link_maintain('menu_test', 'update', 'menu_test_maintain/1', 'Menu link updated');
-    // Load a different page to be sure that we have up to date information.
-    $this->drupalGet('menu_test_maintain/1');
-    $this->assertLink('Menu link updated');
-    $this->assertNoLink('Menu link #1');
-    $this->assertNoLink('Menu link #1-main');
-    $this->assertLink('Menu link #2');
-
-    // Delete all links for the given path.
-    menu_link_maintain('menu_test', 'delete', 'menu_test_maintain/1', '');
-    // Load a different page to be sure that we have up to date information.
-    $this->drupalGet('menu_test_maintain/2');
-    $this->assertNoLink('Menu link updated');
-    $this->assertNoLink('Menu link #1');
-    $this->assertNoLink('Menu link #1-main');
-    $this->assertLink('Menu link #2');
-  }
-
-  /**
    * Tests for menu_name parameter for default menu links.
    */
   protected function doTestMenuName() {
     $admin_user = $this->drupalCreateUser(array('administer site configuration'));
     $this->drupalLogin($admin_user);
-
-    $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu_name_test'));
+    /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
+    $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
+    $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.menu_name_test');
     $menu_link = reset($menu_links);
-    $this->assertEqual($menu_link->menu_name, 'original', 'Menu name is "original".');
+    $this->assertEqual($menu_link->getMenuName(), 'original', 'Menu name is "original".');
 
     // Change the menu_name parameter in menu_test.module, then force a menu
     // rebuild.
     menu_test_menu_name('changed');
-    \Drupal::service('router.builder')->rebuild();
+    $menu_link_manager->rebuild();
 
-    $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu_name_test'));
+    $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.menu_name_test');
     $menu_link = reset($menu_links);
-    $this->assertEqual($menu_link->menu_name, 'changed', 'Menu name was successfully changed after rebuild.');
+    $this->assertEqual($menu_link->getMenuName(), 'changed', 'Menu name was successfully changed after rebuild.');
   }
 
   /**
-   * Tests menu links added in hook_menu_link_defaults_alter().
+   * Tests menu links added in hook_menu_links_discovered_alter().
    */
-  protected function doTestMenuLinkDefaultsAlter() {
+  protected function doTestMenuLinksDiscoveredAlter() {
     // Check that machine name does not need to be defined since it is already
     // set as the key of each menu link.
-    $menu_links = entity_load_multiple_by_properties('menu_link', array('route_name' => 'menu_test.custom'));
+    /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
+    $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
+    $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.custom');
     $menu_link = reset($menu_links);
-    $this->assertEqual($menu_link->machine_name, 'menu_test.custom', 'Menu links added at hook_menu_link_defaults_alter() obtain the machine name from the $links key.');
+    $this->assertEqual($menu_link->getPluginId(), 'menu_test.custom', 'Menu links added at hook_menu_links_discovered_alter() obtain the machine name from the $links key.');
     // Make sure that rebuilding the menu tree does not produce duplicates of
-    // links added by hook_menu_link_defaults_alter().
+    // links added by hook_menu_links_discovered_alter().
     \Drupal::service('router.builder')->rebuild();
     $this->drupalGet('menu-test');
-    $this->assertUniqueText('Custom link', 'Menu links added by hook_menu_link_defaults_alter() do not duplicate after a menu rebuild.');
+    $this->assertUniqueText('Custom link', 'Menu links added by hook_menu_links_discovered_alter() do not duplicate after a menu rebuild.');
   }
 
   /**
    * Tests for menu hierarchy.
    */
   protected function doTestMenuHierarchy() {
-    $parent_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu-test/hierarchy/parent'));
-    $parent_link = reset($parent_links);
-    $child_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu-test/hierarchy/parent/child'));
-    $child_link = reset($child_links);
-    $unattached_child_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu-test/hierarchy/parent/child2/child'));
-    $unattached_child_link = reset($unattached_child_links);
-
-    $this->assertEqual($child_link['plid'], $parent_link['mlid'], 'The parent of a directly attached child is correct.');
-    $this->assertEqual($unattached_child_link['plid'], $parent_link['mlid'], 'The parent of a non-directly attached child is correct.');
-  }
-
-  /**
-   * Test menu maintenance hooks.
-   */
-  protected function doTestMenuItemHooks() {
-    // Create an item.
-    menu_link_maintain('menu_test', 'insert', 'menu_test_maintain/4', 'Menu link #4');
-    $this->assertEqual(menu_test_static_variable(), 'insert', 'hook_menu_link_insert() fired correctly');
-    // Update the item.
-    menu_link_maintain('menu_test', 'update', 'menu_test_maintain/4', 'Menu link updated');
-    $this->assertEqual(menu_test_static_variable(), 'update', 'hook_menu_link_update() fired correctly');
-    // Delete the item.
-    menu_link_maintain('menu_test', 'delete', 'menu_test_maintain/4', '');
-    $this->assertEqual(menu_test_static_variable(), 'delete', 'hook_menu_link_delete() fired correctly');
-  }
-
-  /**
-   * Test menu link 'options' storage and rendering.
-   */
-  protected function doTestMenuLinkOptions() {
-    // Create a menu link with options.
-    $menu_link = entity_create('menu_link', array(
-      'link_title' => 'Menu link options test',
-      'link_path' => 'test-page',
-      'module' => 'menu_test',
-      'options' => array(
-        'attributes' => array(
-          'title' => 'Test title attribute',
-        ),
-        'query' => array(
-          'testparam' => 'testvalue',
-        ),
-      ),
-    ));
-    menu_link_save($menu_link);
-
-    // Load front page.
-    $this->drupalGet('test-page');
-    $this->assertRaw('title="Test title attribute"', 'Title attribute of a menu link renders.');
-    $this->assertRaw('testparam=testvalue', 'Query parameter added to menu link.');
-  }
-
-  /**
-   * Tests the possible ways to set the title for menu items.
-   * Also tests that menu item titles work with string overrides.
-   */
-  protected function doTestMenuItemTitlesCases() {
-
-    // Build array with string overrides.
-    $test_data = array(
-      1 => array('Example title - Case 1' => 'Alternative example title - Case 1'),
-      2 => array('Example title' => 'Alternative example title'),
-      3 => array('Example title' => 'Alternative example title'),
-    );
-
-    foreach ($test_data as $case_no => $override) {
-      $this->menuItemTitlesCasesHelper($case_no);
-      $this->addCustomTranslations('en', array('' => $override));
-      $this->writeCustomTranslations();
-
-      $this->menuItemTitlesCasesHelper($case_no, TRUE);
-      $this->addCustomTranslations('en', array());
-      $this->writeCustomTranslations();
-    }
-  }
-
-  /**
-   * Get a URL and assert the title given a case number. If override is true,
-   * the title is asserted to begin with "Alternative".
-   */
-  protected function menuItemTitlesCasesHelper($case_no, $override = FALSE) {
-    $this->drupalGet('menu-title-test/case' . $case_no);
-    $this->assertResponse(200);
-    $asserted_title = $override ? 'Alternative example title - Case ' . $case_no : 'Example title - Case ' . $case_no;
-    $this->assertTitle($asserted_title . ' | Drupal', format_string('Menu title is: %title.', array('%title' => $asserted_title)), 'Menu');
-  }
-
-  /**
-   * Load the router for a given path.
-   */
-  protected function menuLoadRouter($router_path) {
-    return db_query('SELECT * FROM {menu_router} WHERE path = :path', array(':path' => $router_path))->fetchAssoc();
+    /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
+    $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
+    $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.hierarchy_parent');
+    $parent_link = reset($menu_links);
+    $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.hierarchy_parent.child');
+    $child_link = reset($menu_links);
+    $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.hierarchy_parent.child2.child');
+    $unattached_child_link = reset($menu_links);
+
+    $this->assertEqual($child_link->getParent(), $parent_link->getPluginId(), 'The parent of a directly attached child is correct.');
+    $this->assertEqual($unattached_child_link->getParent(), $parent_link->getPluginId(), 'The parent of a non-directly attached child is correct.');
   }
 
   /**
diff --git a/core/modules/system/src/Tests/System/AdminTest.php b/core/modules/system/src/Tests/System/AdminTest.php
index 950194f..f5609c7 100644
--- a/core/modules/system/src/Tests/System/AdminTest.php
+++ b/core/modules/system/src/Tests/System/AdminTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\System;
 
+use Drupal\Core\Menu\MenuTreeParameters;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -62,9 +63,10 @@ function testAdminPages() {
     // Verify that all visible, top-level administration links are listed on
     // the main administration page.
     foreach ($this->getTopLevelMenuLinks() as $item) {
-      $this->assertLink($item['title']);
-      $this->assertLinkByHref($item['link_path']);
-      $this->assertText($item['localized_options']['attributes']['title']);
+      $this->assertLink($item->getTitle());
+      $this->assertLinkByHref($item->getUrlObject()->toString());
+      // The description should appear below the link.
+      $this->assertText($item->getDescription());
     }
 
     // For each administrative listing page on which the Locale module appears,
@@ -119,26 +121,28 @@ function testAdminPages() {
   /**
    * Returns all top level menu links.
    *
-   * @return \Drupal\menu_link\MenuLinkInterface[]
+   * @return \Drupal\Core\Menu\MenuLinkInterface[]
    */
   protected function getTopLevelMenuLinks() {
-    $route_provider = \Drupal::service('router.route_provider');
-    $routes = array();
-    foreach ($route_provider->getAllRoutes() as $key => $value) {
-      $path = $value->getPath();
-      if (strpos($path, '/admin/') === 0 && count(explode('/', $path)) == 3) {
-        $routes[$key] = $key;
-      }
-    }
-    $menu_link_ids = \Drupal::entityQuery('menu_link')
-      ->condition('route_name', $routes)
-      ->execute();
+    $menu_tree = \Drupal::menuTree();
+
+    // The system.admin link is normally the parent of all top-level admin links.
+    $parameters = new MenuTreeParameters();
+    $parameters->setRoot('system.admin')->excludeRoot()->setTopLevelOnly()->excludeHiddenLinks();
+    $tree = $menu_tree->load(NULL, $parameters);
+    $manipulators = array(
+      array('callable' => 'menu.default_tree_manipulators:checkAccess'),
+      array('callable' => 'menu.default_tree_manipulators:flatten'),
+    );
+    $tree = $menu_tree->transform($tree, $manipulators);
 
-    $menu_items = \Drupal::entityManager()->getStorage('menu_link')->loadMultiple($menu_link_ids);
-    foreach ($menu_items as &$menu_item) {
-      _menu_link_translate($menu_item);
+    // Transform the tree to a list of menu links.
+    $menu_links = array();
+    foreach ($tree as $element) {
+      $menu_links[] = $element->link;
     }
-    return $menu_items;
+
+    return $menu_links;
   }
 
   /**
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index 71b0cec..1fa53e8 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -59,7 +59,7 @@ function template_preprocess_admin_block_content(&$variables) {
       $variables['attributes']['class'][] = 'compact';
     }
     foreach ($variables['content'] as $key => $item) {
-      $variables['content'][$key]['link'] = l($item['title'], $item['link_path'], $item['localized_options']);
+      $variables['content'][$key]['link'] = \Drupal::linkGenerator()->generateFromUrl($item['title'], $item['url']);
       if (!$compact && isset($item['description'])) {
         $variables['content'][$key]['description'] = Xss::filterAdmin($item['description']);
       }
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 111ddfe..1e6e364 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -397,13 +397,13 @@ function hook_page_build(&$page) {
 }
 
 /**
- * Alter links for menus.
+ * Alters all the menu links discovered by the menu link plugin manager.
  *
  * @param array $links
  *   The link definitions to be altered.
  *
  * @return array
- *   An array of default menu links. Each link has a key that is the machine
+ *   An array of discovered menu links. Each link has a key that is the machine
  *   name, which must be unique. By default, use the route name as the
  *   machine name. In cases where multiple links use the same route name, such
  *   as two links to the same page in different menus, or two links using the
@@ -440,7 +440,7 @@ function hook_page_build(&$page) {
  *
  * @ingroup menu
  */
-function hook_menu_link_defaults_alter(&$links) {
+function hook_menu_links_discovered_alter(&$links) {
   // Change the weight and title of the user.logout link.
   $links['user.logout']['weight'] = -10;
   $links['user.logout']['title'] = 'Logout';
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 117dc49..2da9ad1 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -11,6 +11,7 @@
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\StringTranslation\TranslationWrapper;
 use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Menu\MenuTreeParameters;
 use Drupal\block\BlockPluginInterface;
 use Drupal\user\UserInterface;
 use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -1455,76 +1456,48 @@ function system_admin_compact_mode() {
  *   An array of task links.
  */
 function system_get_module_admin_tasks($module, array $info) {
-  $links = &drupal_static(__FUNCTION__);
-
-  if (!isset($links)) {
-    $links = array();
-    $menu_links = entity_get_controller('menu_link')->loadModuleAdminTasks();
-    foreach ($menu_links as $link) {
-      _menu_link_translate($link);
-      if ($link['access']) {
-        $links[$link['machine_name']] = $link;
-      }
-    }
+  $tree = &drupal_static(__FUNCTION__);
+
+  $menu_tree = \Drupal::menuTree();
+
+  if (!isset($tree)) {
+    $parameters = new MenuTreeParameters();
+    $parameters->setRoot('system.admin')->excludeRoot()->excludeHiddenLinks();
+    $tree = $menu_tree->load('system.admin', $parameters);
+    $manipulators = array(
+      array('callable' => 'menu.default_tree_manipulators:checkAccess'),
+      array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
+      array('callable' => 'menu.default_tree_manipulators:flatten'),
+    );
+    $tree = $menu_tree->transform($tree, $manipulators);
   }
 
   $admin_tasks = array();
-  $titles = array();
-  foreach ($links as $item) {
-    if ($item['module'] != $module) {
+  foreach ($tree as $element) {
+    $link = $element->link;
+    if ($link->getProvider() != $module) {
       continue;
     }
-    $machine_name = $item['machine_name'];
-    if (isset($links[$machine_name])) {
-      $task = $links[$machine_name];
-      // The link description, either derived from 'description' in the default
-      // menu link or customized via Menu UI module is used as title attribute.
-      if (!empty($task['localized_options']['attributes']['title'])) {
-        $task['description'] = $task['localized_options']['attributes']['title'];
-        unset($task['localized_options']['attributes']['title']);
-      }
-
-      // Check the admin tasks for duplicate names. If one is found,
-      // append the parent menu item's title to differentiate.
-      $duplicate_path = array_search($task['title'], $titles);
-      if ($duplicate_path !== FALSE) {
-        if ($parent = menu_link_load($task['plid'])) {
-          // Append the parent item's title to this task's title.
-          $task['title'] = t('@original_title (@parent_title)', array('@original_title' => $task['title'], '@parent_title' => $parent['title']));
-        }
-        if ($parent = menu_link_load($admin_tasks[$duplicate_path]['plid'])) {
-          // Append the parent item's title to the duplicated task's title.
-          // We use $links[$duplicate_path] in case there are triplicates.
-          $admin_tasks[$duplicate_path]['title'] = t('@original_title (@parent_title)', array('@original_title' => $links[$duplicate_path]['title'], '@parent_title' => $parent['title']));
-        }
-      }
-      else {
-        $titles[$machine_name] = $task['title'];
-      }
-
-      $admin_tasks[$machine_name] = $task;
-    }
+    $admin_tasks[] = array(
+      'title' => $link->getTitle(),
+      'description' => $link->getDescription(),
+      'url' => $link->getUrlObject(),
+    );
   }
 
   // Append link for permissions.
   if (\Drupal::moduleHandler()->implementsHook($module, 'permission')) {
     /** @var \Drupal\Core\Access\AccessManager $access_manager */
     $access_manager = \Drupal::service('access_manager');
-    /** @var \Drupal\menu_link\MenuLinkStorageInterface $menu_link_storage */
-    $menu_link_storage = \Drupal::entityManager()
-      ->getStorage('menu_link');
     if ($access_manager->checkNamedRoute('user.admin_permissions', array(), \Drupal::currentUser())) {
-      $path = \Drupal::urlGenerator()
-        ->getPathFromRoute('user.admin_permissions');
-      $options = array();
-      $options['fragment'] = 'module-' . $module;
-      $menu_link = $menu_link_storage->create(array(
-        'route_name' => 'user.admin_permissions',
-        'link_path' => $path,
+      /** @var \Drupal\Core\Url $url */
+      $url = new \Drupal\Core\Url('user.admin_permissions');
+      $url->setOption('fragment', 'module-' . $module);
+      $admin_tasks["user.admin_permissions.$module"] = array(
         'title' => t('Configure @module permissions', array('@module' => $info['name'])),
-        'localized_options' => $options
-      ));
-      $admin_tasks["user.admin.people.permissions.$module"] = $menu_link;
+        'description' => '',
+        'url' => $url,
+      );
     }
   }
 
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 35e17f7..c22d93b 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -382,7 +382,7 @@ system.admin_config:
   path: '/admin/config'
   defaults:
     _content: '\Drupal\system\Controller\SystemController::overview'
-    path: 'admin/config'
+    link_id: 'system.admin_config'
     _title: 'Configuration'
   requirements:
     _permission: 'access administration pages'
@@ -414,7 +414,7 @@ system.admin_content:
   path: '/admin/content'
   defaults:
     _content: '\Drupal\system\Controller\SystemController::overview'
-    path: 'admin/content'
+    link_id: 'system.admin_content'
     _title: 'Content'
   requirements:
     _permission: 'access administration pages'
diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml
index 2b7046c..9e09d19 100644
--- a/core/modules/system/system.services.yml
+++ b/core/modules/system/system.services.yml
@@ -5,7 +5,7 @@ services:
       - { name: access_check, applies_to: _access_system_cron }
   system.manager:
     class: Drupal\system\SystemManager
-    arguments: ['@module_handler', '@database', '@entity.manager', '@request_stack']
+    arguments: ['@module_handler', '@database', '@entity.manager', '@request_stack', '@menu.link_tree', '@menu.active_trail']
   system.breadcrumb.default:
     class: Drupal\system\PathBasedBreadcrumbBuilder
     arguments: ['@router.request_context', '@access_manager', '@router', '@path_processor_manager', '@config.factory',  '@title_resolver', '@current_user']
diff --git a/core/modules/system/templates/menu-tree.html.twig b/core/modules/system/templates/menu-tree.html.twig
new file mode 100644
index 0000000..03acca6
--- /dev/null
+++ b/core/modules/system/templates/menu-tree.html.twig
@@ -0,0 +1,40 @@
+{#
+/**
+ * @file
+ * Default theme implementation for a menu tree.
+ *
+ * Available variables:
+ * - attributes: Attributes for the UL containing the tree of links.
+ * - tree: Menu tree to be output.
+ * - heading: (optional) A heading to precede the links.
+ *   - text: The heading text.
+ *   - level: The heading level (e.g. 'h2', 'h3').
+ *   - attributes: (optional) A keyed list of attributes for the heading.
+ *   If the heading is a string, it will be used as the text of the heading and
+ *   the level will default to 'h2'.
+ *
+ *   Headings should be used on navigation menus and any list of links that
+ *   consistently appears on multiple pages. To make the heading invisible use
+ *   the 'visually-hidden' CSS class. Do not use 'display:none', which
+ *   removes it from screen-readers and assistive technology. Headings allow
+ *   screen-reader and keyboard only users to navigate to or skip the links.
+ *   See http://juicystudio.com/article/screen-readers-display-none.php and
+ *   http://www.w3.org/TR/WCAG-TECHS/H42.html for more information.
+ *
+ * @see template_preprocess_menu_tree()
+ *
+ * @ingroup themeable
+ */
+#}
+{% if tree -%}
+  {%- if heading -%}
+    {%- if heading.level -%}
+      <{{ heading.level }}{{ heading.attributes }}>{{ heading.text }}</{{ heading.level }}>
+    {%- else -%}
+      <h2{{ heading.attributes }}>{{ heading.text }}</h2>
+    {%- endif -%}
+  {%- endif -%}
+  <ul{{ attributes }}>
+    {{ tree }}
+  </ul>
+{%- endif %}
diff --git a/core/modules/system/tests/modules/menu_test/menu_test.links.menu.yml b/core/modules/system/tests/modules/menu_test/menu_test.links.menu.yml
index d9e7a39..914fbd5 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.links.menu.yml
+++ b/core/modules/system/tests/modules/menu_test/menu_test.links.menu.yml
@@ -69,3 +69,13 @@ menu_test.menu-title-test.case3:
 menu_test.context:
   title: ''
   route_name: menu_test.context
+
+menu_test.parent:
+  title: 'Test menu_name parent'
+  route_name: menu_test.menu_name_test
+  menu_name: test
+
+menu_test.child:
+  title: 'Test menu_name child'
+  route_name: menu_test.menu_name_test
+  parent: menu_test.parent
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 c222ce8..b4f76a2 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.module
+++ b/core/modules/system/tests/modules/menu_test/menu_test.module
@@ -5,12 +5,10 @@
  * Module that implements various hooks for menu tests.
  */
 
-use Drupal\menu_link\Entity\MenuLink;
-
 /**
- * Implements hook_menu_link_defaults_alter().
+ * Implements hook_menu_links_discovered_alter().
  */
-function menu_test_menu_link_defaults_alter(&$links) {
+function menu_test_menu_links_discovered_alter(&$links) {
   // Many of the machine names here are slightly different from the route name.
   // Since the machine name is arbitrary, this helps ensure that core does not
   // add mistaken assumptions about the correlation.
@@ -97,29 +95,6 @@ function menu_test_callback() {
 }
 
 /**
- * Page callback: Tests menu_test_menu_tree_set_path().
- *
- * Retrieves the current menu path and if the menu path is not empty updates
- * the menu path that is used to determine the active menu trail.
- *
- * @return string
- *   A string that can be used for comparison.
- *
- * @see menu_test_menu().
- *
- * @deprecated Use \Drupal\menu_test\Controller\MenuTestController::menuTrail()
- */
-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->setPath($menu_path['menu_name'], $menu_path['path']);
-  }
-  return 'This is menu_test_menu_trail_callback().';
-}
-
-/**
  * Page callback: Tests the theme negotiation functionality.
  *
  * @param bool $inherited
@@ -167,44 +142,6 @@ function menu_test_menu_name($new_name = '') {
 }
 
 /**
- * Implements hook_ENTITY_TYPE_insert() for menu_link entities.
- */
-function menu_test_menu_link_insert(MenuLink $item) {
-  menu_test_static_variable('insert');
-}
-
-/**
- * Implements hook_ENTITY_TYPE_update() for menu_link entities.
- */
-function menu_test_menu_link_update(MenuLink $item) {
-  menu_test_static_variable('update');
-}
-
-/**
- * Implements hook_ENTITY_TYPE_delete() for menu_link entities.
- */
-function menu_test_menu_link_delete(MenuLink $item) {
-  menu_test_static_variable('delete');
-}
-
-/**
- * Sets a static variable for testing hook results.
- *
- * @param null|string $value
- *   (optional) The value to set or NULL to return the current value.
- *
- * @return null|string
- *   A text string for comparison to test assertions.
- */
-function menu_test_static_variable($value = NULL) {
-  static $variable;
-  if (!empty($value)) {
-    $variable = $value;
-  }
-  return $variable;
-}
-
-/**
  * Title callback: Concatenates the title and case number.
  *
  * @param string $title
diff --git a/core/modules/system/tests/modules/test_page_test/test_page_test.links.menu.yml b/core/modules/system/tests/modules/test_page_test/test_page_test.links.menu.yml
new file mode 100644
index 0000000..291fd70
--- /dev/null
+++ b/core/modules/system/tests/modules/test_page_test/test_page_test.links.menu.yml
@@ -0,0 +1,4 @@
+test_page_test.test_page:
+  route_name: test_page_test.test_page
+  title: 'Test front page link'
+  weight: 0
diff --git a/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php b/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php
index a227a7f..c01ac71 100644
--- a/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php
+++ b/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php
@@ -125,30 +125,16 @@ function testModuleStatusChangeSubtreesHashCacheClear() {
   }
 
   /**
-   * Tests toolbar_menu_link_update() hook implementation.
+   * Tests toolbar cache tags implementation.
    */
   function testMenuLinkUpdateSubtreesHashCacheClear() {
-    // Get subtree items for the admin menu.
-    $query = \Drupal::entityQuery('menu_link');
-    for ($i = 1; $i <= 3; $i++) {
-      $query->sort('p' . $i, 'ASC');
-    }
-    $query->condition('menu_name', 'admin');
-    $query->condition('depth', '2', '>=');
-
-    // Build an ordered array of links using the query result object.
-    $links = array();
-    if ($result = $query->execute()) {
-      $links = menu_link_load_multiple($result);
-    }
-    // Get the first link in the set.
-    $links = array_values($links);
-    $link = array_shift($links);
+    // The ID of a (any) admin menu link.
+    $admin_menu_link_id = 'system.admin_config_development';
 
     // Disable the link.
     $edit = array();
     $edit['enabled'] = FALSE;
-    $this->drupalPostForm("admin/structure/menu/item/" . $link['mlid'] . "/edit", $edit, t('Save'));
+    $this->drupalPostForm("admin/structure/menu/link/" . $admin_menu_link_id . "/edit", $edit, t('Save'));
     $this->assertResponse(200);
     $this->assertText('The menu link has been saved.');
 
diff --git a/core/modules/toolbar/toolbar.info.yml b/core/modules/toolbar/toolbar.info.yml
index 269f7ec..6967bf6 100644
--- a/core/modules/toolbar/toolbar.info.yml
+++ b/core/modules/toolbar/toolbar.info.yml
@@ -6,4 +6,3 @@ package: Core
 version: VERSION
 dependencies:
   - breakpoint
-  - menu_link
diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module
index d8e5363..9963b37 100644
--- a/core/modules/toolbar/toolbar.module
+++ b/core/modules/toolbar/toolbar.module
@@ -6,13 +6,13 @@
  */
 
 use Drupal\Core\Cache\Cache;
+use Drupal\Core\Menu\MenuTreeParameters;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Template\Attribute;
 use Drupal\Component\Datetime\DateTimePlus;
 use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\String;
-use Drupal\menu_link\MenuLinkInterface;
 use Drupal\user\RoleInterface;
 use Drupal\user\UserInterface;
 
@@ -326,8 +326,6 @@ function toolbar_pre_render_item($element) {
  * Implements hook_toolbar().
  */
 function toolbar_toolbar() {
-  $items = array();
-
   // The 'Home' tab is a simple link, with no corresponding tray.
   $items['home'] = array(
     '#type' => 'toolbar_item',
@@ -352,33 +350,13 @@ function toolbar_toolbar() {
     '#weight' => -20,
   );
 
-  // Retrieve the administration menu from the database.
-  $tree = toolbar_get_menu_tree();
-
-  // 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(
-      '#type' => 'container',
-      '#attributes' => array(
-        'class' => array('toolbar-menu-administration'),
-      ),
-      'administration_menu' => $menu_tree->renderTree($tree),
-    ),
-  );
-
   // To conserve bandwidth, we only include the top-level links in the HTML.
   // The subtrees are fetched through a JSONP script that is generated at the
   // toolbar_subtrees route. We provide the JavaScript requesting that JSONP
   // script here with the hash parameter that is needed for that route.
   // @see toolbar_subtrees_jsonp()
   $langcode = \Drupal::languageManager()->getCurrentLanguage()->id;
-  $menu['toolbar_administration']['#attached']['js'][] = array(
+  $subtrees_attached['js'][] = array(
     'type' => 'setting',
     'data' => array('toolbar' => array(
       'subtreesHash' => _toolbar_get_subtrees_hash($langcode),
@@ -405,7 +383,19 @@ function toolbar_toolbar() {
         'data-drupal-subtrees' => '',
       ),
     ),
-    'tray' => $menu,
+    'tray' => array(
+      '#heading' => t('Administration menu'),
+      '#attached' => $subtrees_attached,
+      'toolbar_administration' => array(
+        '#pre_render' => array(
+          'toolbar_prerender_toolbar_administration_tray',
+        ),
+        '#type' => 'container',
+        '#attributes' => array(
+          'class' => array('toolbar-menu-administration'),
+        ),
+      ),
+    ),
     '#weight' => -15,
   );
 
@@ -413,90 +403,102 @@ function toolbar_toolbar() {
 }
 
 /**
- * Gets only the top level items below the 'admin' path.
+ * Renders the toolbar's administration tray.
  *
- * @return
- *   An array containing a menu tree of top level items below the 'admin' path.
+ * @param array $element
+ *   A renderable array.
+ *
+ * @return array
+ *   The updated renderable array.
+ *
+ * @see drupal_render()
  */
-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')
-    ->condition('link_path', 'admin');
-  $result = $query->execute();
-  if (!empty($result)) {
-    $admin_link = menu_link_load(reset($result));
-    $tree = $menu_tree->buildTree('admin', array(
-      'expanded' => array($admin_link['mlid']),
-      'min_depth' => $admin_link['depth'] + 1,
-      'max_depth' => $admin_link['depth'] + 1,
-    ));
-  }
-
-  return $tree;
+function toolbar_prerender_toolbar_administration_tray(array $element) {
+  $menu_tree = \Drupal::menuTree();
+  // Render the top-level administration menu links.
+  $parameters = new MenuTreeParameters();
+  $parameters->setRoot('system.admin')->excludeRoot()->setTopLevelOnly()->excludeHiddenLinks();
+  $tree = $menu_tree->load(NULL, $parameters);
+  $manipulators = array(
+    array('callable' => 'menu.default_tree_manipulators:checkAccess'),
+    array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
+    array('callable' => 'toolbar_menu_navigation_links'),
+  );
+  $tree = $menu_tree->transform($tree, $manipulators);
+  $element['administration_menu'] = $menu_tree->build($tree);
+  return $element;
 }
 
 /**
- * Generates an array of links from a menu tree array.
+ * Adds toolbar-specific attributes to the menu link tree.
  *
- * Based on menu_navigation_links(). Adds path based IDs and icon placeholders
- * to the links.
+ * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
+ *   The menu link tree to manipulate.
  *
- * @return
- *   An array of links as defined above.
+ * @return \Drupal\Core\Menu\MenuLinkTreeElement[]
+ *   The manipulated menu link tree.
  */
-function toolbar_menu_navigation_links(&$tree) {
-  foreach ($tree as $key => $item) {
-    // Configure sub-items.
-    if (!empty($item['below'])) {
-      toolbar_menu_navigation_links($tree[$key]['below']);
+function toolbar_menu_navigation_links(array $tree) {
+  foreach ($tree as $element) {
+    if ($element->subtree) {
+      toolbar_menu_navigation_links($element->subtree);
     }
+
     // Make sure we have a path specific ID in place, so we can attach icons
-    // and behaviors to the items.
-    $tree[$key]['link']['localized_options']['attributes'] = array(
-      'id' => 'toolbar-link-' . str_replace(array('/', '<', '>'), array('-', '', ''), $item['link']['link_path']),
-      'class' => array(
-        'toolbar-icon',
-        'toolbar-icon-' . strtolower(str_replace(' ', '-', $item['link']['link_title'])),
-      ),
-      'title' => String::checkPlain($item['link']['description']),
-    );
+    // and behaviors to the menu links.
+    $link = $element->link;
+    $url = $link->getUrlObject();
+    if ($url->isExternal()) {
+      // This is an unusual case, so just get a distinct, safe string.
+      $id = substr(Crypt::hashBase64($url->getPath()), 0, 16);
+    }
+    else {
+      $id = str_replace(array('.', '<', '>'), array('-', '', ''), $url->getRouteName());
+    }
+
+    // Get the non-localized title to make the icon class.
+    $definition = $link->getPluginDefinition();
+
+    $element->options['attributes']['id'] = 'toolbar-link-' . $id;
+    $element->options['attributes']['class'][] = 'toolbar-icon';
+    // @todo Change to use the plugin ID as class since titles might change.
+    //   https://www.drupal.org/node/2310365
+    $element->options['attributes']['class'][] = 'toolbar-icon-' . strtolower(str_replace(' ', '-', $definition['title']));
+    $element->options['attributes']['title'] = String::checkPlain($link->getDescription());
   }
+  return $tree;
 }
 
 /**
  * Returns the rendered subtree of each top-level toolbar link.
  */
 function toolbar_get_rendered_subtrees() {
+  $menu_tree = \Drupal::menuTree();
+  $parameters = new MenuTreeParameters();
+  $parameters->setRoot('system.admin')->excludeRoot()->setMaxDepth(3)->excludeHiddenLinks();
+  $tree = $menu_tree->load(NULL, $parameters);
+  $manipulators = array(
+    array('callable' => 'menu.default_tree_manipulators:checkAccess'),
+    array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
+    array('callable' => 'toolbar_menu_navigation_links'),
+  );
+  $tree = $menu_tree->transform($tree, $manipulators);
   $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'];
-    if (!$item['hidden'] && $item['access']) {
-      if ($item['has_children']) {
-        $query = \Drupal::entityQuery('menu_link')
-          ->condition('has_children', 1);
-        for ($i=1; $i <= $item['depth']; $i++) {
-          $query->condition('p' . $i, $item['p' . $i]);
-        }
-        $parents = $query->execute();
-        $subtree = $menu_tree->buildTree($item['menu_name'], array('expanded' => $parents, 'min_depth' => $item['depth']+1));
-        toolbar_menu_navigation_links($subtree);
-        $subtree = $menu_tree->renderTree($subtree);
-        $subtree = drupal_render($subtree);
-      }
-      else {
-        $subtree = '';
-      }
-
-      $id = str_replace(array('/', '<', '>'), array('-', '', ''), $item['link_path']);
-      $subtrees[$id] = $subtree;
+  foreach ($tree as $element) {
+    /** @var \Drupal\Core\Menu\MenuLinkInterface $item */
+    $link = $element->link;
+    if ($element->subtree) {
+      $subtree = $menu_tree->build($element->subtree);
+      $output = drupal_render($subtree);
     }
+    else {
+      $output = '';
+    }
+    // Many routes have dots as route name, while some special ones like <front>
+    // have <> characters in them.
+    $id = str_replace(array('.', '<', '>'), array('-', '', '' ), $link->getUrlObject()->getRouteName());
+
+    $subtrees[$id] = $output;
   }
   return $subtrees;
 }
@@ -523,7 +525,7 @@ function _toolbar_get_subtrees_hash($langcode) {
     // caches later, based on the user's ID regardless of language.
     // Clear the cache when the 'locale' tag is deleted. This ensures a fresh
     // subtrees rendering when string translations are made.
-    \Drupal::cache('toolbar')->set($cid, $hash, Cache::PERMANENT, array('user' => array($uid), 'locale' => TRUE,));
+    \Drupal::cache('toolbar')->set($cid, $hash, Cache::PERMANENT, array('user' => array($uid), 'locale' => TRUE, 'menu' => 'admin'));
   }
   return $hash;
 }
@@ -543,15 +545,6 @@ function toolbar_modules_uninstalled($modules) {
 }
 
 /**
- * Implements hook_ENTITY_TYPE_update() for menu_link entities.
- */
-function toolbar_menu_link_update(MenuLinkInterface $menu_link) {
-  if ($menu_link->menu_name === 'admin') {
-    _toolbar_clear_user_cache();
-  }
-}
-
-/**
  * Implements hook_ENTITY_TYPE_update() for user entities.
  */
 function toolbar_user_update(UserInterface $user) {
diff --git a/core/modules/user/src/Plugin/Menu/MyAccountMenuLink.php b/core/modules/user/src/Plugin/Menu/MyAccountMenuLink.php
new file mode 100644
index 0000000..91768cd
--- /dev/null
+++ b/core/modules/user/src/Plugin/Menu/MyAccountMenuLink.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Plugin\Menu\MyAccountMenuLink.
+ */
+
+namespace Drupal\user\Plugin\Menu;
+
+use Drupal\Core\Menu\MenuLinkDefault;
+
+/**
+ * Provides custom logic for the user.page menu link.
+ */
+class MyAccountMenuLink extends MenuLinkDefault {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isHidden() {
+    // The path 'user' must be accessible for anonymous users, but only visible
+    // for authenticated users. Authenticated users should see "My account", but
+    // anonymous users should not see it at all.
+    // @todo Re-write this as a link to user.view with dynamic route parameters
+    //   to affect access since hidden should not be dynamic.
+    //   https://www.drupal.org/node/2306991
+    return $this->pluginDefinition['hidden'] || \Drupal::currentUser()->isAnonymous();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isCacheable() {
+    return FALSE;
+  }
+
+}
diff --git a/core/modules/user/src/Tests/UserAccountLinksTests.php b/core/modules/user/src/Tests/UserAccountLinksTests.php
index d57e39b..e602120 100644
--- a/core/modules/user/src/Tests/UserAccountLinksTests.php
+++ b/core/modules/user/src/Tests/UserAccountLinksTests.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\user\Tests;
 
+use Drupal\Core\Menu\MenuTreeParameters;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -43,14 +44,14 @@ function testSecondaryMenu() {
     // For a logged-in user, expect the secondary menu to have links for "My
     // account" and "Log out".
     $link = $this->xpath('//ul[@class=:menu_class]/li/a[contains(@href, :href) and text()=:text]', array(
-      ':menu_class' => 'links',
+      ':menu_class' => 'menu',
       ':href' => 'user',
       ':text' => 'My account',
     ));
     $this->assertEqual(count($link), 1, 'My account link is in secondary menu.');
 
     $link = $this->xpath('//ul[@class=:menu_class]/li/a[contains(@href, :href) and text()=:text]', array(
-      ':menu_class' => 'links',
+      ':menu_class' => 'menu',
       ':href' => 'user/logout',
       ':text' => 'Log out',
     ));
@@ -61,13 +62,15 @@ function testSecondaryMenu() {
     $this->drupalGet('<front>');
 
     // For a logged-out user, expect no secondary links.
-    /** @var \Drupal\menu_link\MenuTreeInterface $menu_tree */
-    $menu_tree = \Drupal::service('menu_link.tree');
-    $tree = $menu_tree->buildTree('account');
+    $menu_tree = \Drupal::menuTree();
+    $tree = $menu_tree->load('account', new MenuTreeParameters());
+    $manipulators = array(
+      array('callable' => 'menu.default_tree_manipulators:checkAccess'),
+    );
+    $tree = $menu_tree->transform($tree, $manipulators);
     $this->assertEqual(count($tree), 1, 'The secondary links menu contains only one menu link.');
-    $link = reset($tree);
-    $link = $link['link'];
-    $this->assertTrue((bool) $link->hidden, 'The menu link is hidden.');
+    $element = reset($tree);
+    $this->assertTrue($element->link->isHidden(), 'The menu link is hidden.');
   }
 
   /**
@@ -80,7 +83,7 @@ function testDisabledAccountLink() {
     // Verify that the 'My account' link exists before we check for its
     // disappearance.
     $link = $this->xpath('//ul[@class=:menu_class]/li/a[contains(@href, :href) and text()=:text]', array(
-      ':menu_class' => 'links',
+      ':menu_class' => 'menu',
       ':href' => 'user',
       ':text' => 'My account',
     ));
@@ -94,10 +97,7 @@ function testDisabledAccountLink() {
     $this->assertFieldChecked((string) $label[0], "The 'My account' link is enabled by default.");
 
     // Disable the 'My account' link.
-    $input = $this->xpath('//input[@id=:field_id]/@name', array(':field_id' => (string)$label[0]));
-    $edit = array(
-      (string) $input[0] => FALSE,
-    );
+    $edit['links[menu_plugin_id:user.page][enabled]'] = FALSE;
     $this->drupalPostForm('admin/structure/menu/manage/account', $edit, t('Save'));
 
     // Get the homepage.
@@ -105,7 +105,7 @@ function testDisabledAccountLink() {
 
     // Verify that the 'My account' link does not appear when disabled.
     $link = $this->xpath('//ul[@class=:menu_class]/li/a[contains(@href, :href) and text()=:text]', array(
-      ':menu_class' => 'links',
+      ':menu_class' => 'menu',
       ':href' => 'user',
       ':text' => 'My account',
     ));
diff --git a/core/modules/user/user.links.menu.yml b/core/modules/user/user.links.menu.yml
index bc421f0..3490a8d 100644
--- a/core/modules/user/user.links.menu.yml
+++ b/core/modules/user/user.links.menu.yml
@@ -3,6 +3,7 @@ user.page:
   weight: -10
   route_name: user.page
   menu_name: account
+  class: Drupal\user\Plugin\Menu\MyAccountMenuLink
 user.logout:
   title: 'Log out'
   route_name: user.logout
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 2253d1c..0b3d453 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -634,19 +634,6 @@ function template_preprocess_username(&$variables) {
 }
 
 /**
- * Implements hook_menu_link_presave().
- */
-function user_menu_link_presave(MenuLink $menu_link) {
-  // The path 'user' must be accessible for anonymous users, but only visible
-  // for authenticated users. Authenticated users should see "My account", but
-  // anonymous users should not see it at all. Therefore, invoke
-  // user_menu_link_load() to conditionally hide the link.
-  if ($menu_link->machine_name == 'user.page') {
-    $menu_link->options['alter'] = TRUE;
-  }
-}
-
-/**
  * Implements hook_menu_breadcrumb_alter().
  */
 function user_menu_breadcrumb_alter(&$active_trail, $item) {
@@ -658,16 +645,6 @@ function user_menu_breadcrumb_alter(&$active_trail, $item) {
 }
 
 /**
- * Implements hook_translated_menu_link_alter().
- */
-function user_translated_menu_link_alter(MenuLink &$menu_link) {
-  // Hide the "User account" link for anonymous users.
-  if ($menu_link->machine_name == 'user.page' && \Drupal::currentUser()->isAnonymous()) {
-    $menu_link->hidden = 1;
-  }
-}
-
-/**
  * Finalizes the login process and logs in a user.
  *
  * The function logs in the user, records a watchdog message about the new
diff --git a/core/modules/views/src/Plugin/Derivative/ViewsMenuLink.php b/core/modules/views/src/Plugin/Derivative/ViewsMenuLink.php
new file mode 100644
index 0000000..e83af2b
--- /dev/null
+++ b/core/modules/views/src/Plugin/Derivative/ViewsMenuLink.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\Plugin\Derivative\ViewsMenuLink.
+ */
+
+namespace Drupal\views\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DeriverInterface;
+use Drupal\views\Views;
+
+/**
+ * Provides menu links for Views.
+ *
+ * @see \Drupal\views\Plugin\Menu\ViewsMenuLink
+ */
+class ViewsMenuLink implements DeriverInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
+    if (!isset($this->derivatives)) {
+      $this->getDerivativeDefinitions($base_plugin_definition);
+    }
+    if (isset($this->derivatives[$derivative_id])) {
+      return $this->derivatives[$derivative_id];
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    $links = array();
+    // @todo Replace uses_hook_menu with an annotation.
+    //   https://www.drupal.org/node/2310371
+    $views = Views::getApplicableViews('uses_hook_menu');
+    foreach ($views as $data) {
+      /** @var \Drupal\views\ViewExecutable $view */
+      list($view, $display_id) = $data;
+      if ($result = $view->getMenuLinks($display_id)) {
+        foreach ($result as $link_id => $link) {
+          $links[$link_id] = $link + $base_plugin_definition;
+        }
+      }
+    }
+
+    return $links;
+  }
+
+}
diff --git a/core/modules/views/src/Plugin/Menu/Form/ViewsMenuLinkForm.php b/core/modules/views/src/Plugin/Menu/Form/ViewsMenuLinkForm.php
new file mode 100644
index 0000000..d97b204
--- /dev/null
+++ b/core/modules/views/src/Plugin/Menu/Form/ViewsMenuLinkForm.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\Plugin\Menu\Form\ViewsMenuLinkForm.
+ */
+
+namespace Drupal\views\Plugin\Menu\Form;
+
+use Drupal\Core\Menu\Form\MenuLinkDefaultForm;
+
+/**
+ * Provides a form to edit Views menu links.
+ *
+ * This provides the feature to edit the title and description, in contrast to
+ * the default menu link form.
+ *
+ * @see \Drupal\views\Plugin\Menu\ViewsMenuLink
+ */
+class ViewsMenuLinkForm extends MenuLinkDefaultForm {
+
+  /**
+   * The edited views menu link.
+   *
+   * @var \Drupal\views\Plugin\Menu\ViewsMenuLink
+   */
+  protected $menuLink;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, array &$form_state) {
+
+    // Put the title field first.
+    $form['title'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Title'),
+      // @todo Ensure that the view is not loaded with a localized title.
+      //   https://www.drupal.org/node/2309507
+      '#default_value' => $this->menuLink->getTitle(),
+      '#weight' => -10,
+    );
+
+    $form['description'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Description'),
+      '#description' => $this->t('Shown when hovering over the menu link.'),
+      // @todo Ensure that the view is not loaded with a localized description.
+      //   https://www.drupal.org/node/2309507
+      '#default_value' => $this->menuLink->getDescription(),
+      '#weight' => -5,
+    );
+
+    $form += parent::buildConfigurationForm($form, $form_state);
+
+    $form['info']['#weight'] = -8;
+    $form['path']['#weight'] = -7;
+
+    $view = $this->menuLink->loadView();
+    $id = $view->storage->id();
+    $label = $view->storage->label();
+    if ($this->moduleHandler->moduleExists('views_ui')) {
+      $message = $this->t('This link is provided by the Views module. The path can be changed by editing the view <a href="@url">@label</a>', array('@url' => \Drupal::url('views_ui.edit', array('view' => $id)), '@label' => $label));
+    }
+    else {
+      $message = $this->t('This link is provided by the Views module from view %label.', array('%label' => $label));
+    }
+    $form['info']['#title'] = $message;
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function extractFormValues(array &$form, array &$form_state) {
+    $definition = parent::extractFormValues($form, $form_state);
+    $definition['title'] = $form_state['values']['title'];
+    $definition['description'] = $form_state['values']['description'];
+
+    return $definition;
+  }
+
+}
diff --git a/core/modules/views/src/Plugin/Menu/ViewsMenuLink.php b/core/modules/views/src/Plugin/Menu/ViewsMenuLink.php
new file mode 100644
index 0000000..4e164d7
--- /dev/null
+++ b/core/modules/views/src/Plugin/Menu/ViewsMenuLink.php
@@ -0,0 +1,157 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views\Plugin\Menu\ViewsMenuLink.
+ */
+
+namespace Drupal\views\Plugin\Menu;
+
+use Drupal\Core\Menu\MenuLinkBase;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\views\ViewExecutableFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines menu links provided by views.
+ *
+ * @see \Drupal\views\Plugin\Derivative\ViewsMenuLink
+ */
+class ViewsMenuLink extends MenuLinkBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $overrideAllowed = array(
+    'menu_name' => 1,
+    'parent' => 1,
+    'weight' => 1,
+    'expanded' => 1,
+    'hidden' => 1,
+    'title' => 1,
+    'description' => 1,
+    'metadata' => 1,
+  );
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * The view executable factory.
+   *
+   * @var \Drupal\views\ViewExecutableFactory
+   */
+  protected $viewExecutableFactory;
+
+  /**
+   * The view executable of the menu link.
+   *
+   * @var \Drupal\views\ViewExecutable
+   */
+  protected $view;
+
+  /**
+   * Constructs a new ViewsMenuLink.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager
+   * @param \Drupal\views\ViewExecutableFactory $view_executable_factory
+   *   The view executable factory
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, ViewExecutableFactory $view_executable_factory) {
+    $this->configuration = $configuration;
+    $this->pluginId = $plugin_id;
+    $this->pluginDefinition = $plugin_definition;
+
+    $this->entityManager = $entity_manager;
+    $this->viewExecutableFactory = $view_executable_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('entity.manager'),
+      $container->get('views.executable')
+    );
+  }
+
+  /**
+   * Initializes the proper view.
+   *
+   * @return \Drupal\views\ViewExecutable
+   *   The view executable.
+   */
+  public function loadView() {
+    if (empty($this->view)) {
+      $metadata = $this->getMetaData();
+      $view_id = $metadata['view_id'];
+      $display_id = $metadata['display_id'];
+      $view_entity = $this->entityManager->getStorage('view')->load($view_id);
+      $view = $this->viewExecutableFactory->get($view_entity);
+      $view->setDisplay($display_id);
+      $view->initDisplay();
+      $this->view = $view;
+    }
+    return $this->view;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTitle() {
+    // @todo Get the translated value from the config without instantiating the
+    //   view. https://www.drupal.org/node/2310379
+    return $this->loadView()->display_handler->getOption('menu')['title'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return $this->loadView()->display_handler->getOption('menu')['description'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function updateLink(array $new_definition_values, $persist) {
+    $overrides = array_intersect_key($new_definition_values, $this->overrideAllowed);
+    if ($persist) {
+      $view = $this->loadView();
+      $display = &$view->storage->getDisplay($view->current_display);
+      // Just save the title to the original view.
+      $changed = FALSE;
+      foreach (array('title' => 'title', 'weight' => 'weight', 'menu' => 'name', 'description' => 'description') as $definition_key => $views_key) {
+        if ($display['display_options']['menu'][$views_key] != $new_definition_values[$definition_key]) {
+          $display['display_options']['menu'][$views_key] = $new_definition_values[$definition_key];
+          $changed = TRUE;
+        }
+      }
+      if ($changed) {
+        // @todo Improve this to not trigger a full rebuild of everything, if we
+        //   just changed some properties. https://www.drupal.org/node/2310389
+        $view->storage->save();
+      }
+    }
+    // Update the definition.
+    $this->pluginDefinition = $overrides + $this->pluginDefinition;
+    return $this->pluginDefinition;
+  }
+
+}
diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
index 3d3d6a5..6ce6c07 100644
--- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
+++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
@@ -2119,17 +2119,14 @@ public function renderMoreLink() {
   }
 
   /**
-   * Creates menu links, if this display provides some.
-   *
-   * @param array $existing_links
-   *   An array of already existing menu items provided by drupal.
+   * Gets menu links, if this display provides some.
    *
    * @return array
    *   The menu links registers for this display.
    *
-   * @see hook_menu_link_defaults()
+   * @see \Drupal\views\Plugin\Derivative\ViewsMenuLink
    */
-  public function executeHookMenuLinkDefaults(array &$existing_links) {
+  public function getMenuLinks() {
     return array();
   }
 
diff --git a/core/modules/views/src/Plugin/views/display/PathPluginBase.php b/core/modules/views/src/Plugin/views/display/PathPluginBase.php
index 44127ec..a6e1759 100644
--- a/core/modules/views/src/Plugin/views/display/PathPluginBase.php
+++ b/core/modules/views/src/Plugin/views/display/PathPluginBase.php
@@ -279,7 +279,7 @@ public function alterRoutes(RouteCollection $collection) {
   /**
    * {@inheritdoc}
    */
-  public function executeHookMenuLinkDefaults(array &$existing_links) {
+  public function getMenuLinks() {
     $links = array();
 
     // Replace % with the link to our standard views argument loader
@@ -299,7 +299,10 @@ public function executeHookMenuLinkDefaults(array &$existing_links) {
     $view_route_names = $this->state->get('views.view_route_names') ?: array();
 
     $path = implode('/', $bits);
-    $menu_link_id = 'views.' . str_replace('/', '.', $path);
+    $view_id = $this->view->storage->id();
+    $display_id = $this->display['id'];
+    $view_id_display =  "{$view_id}.{$display_id}";
+    $menu_link_id = 'views.' . str_replace('/', '.', $view_id_display);
 
     if ($path) {
       $menu = $this->getOption('menu');
@@ -307,12 +310,11 @@ public function executeHookMenuLinkDefaults(array &$existing_links) {
         $links[$menu_link_id] = array();
         // Some views might override existing paths, so we have to set the route
         // name based upon the altering.
-        $view_id_display =  "{$this->view->storage->id()}.{$this->display['id']}";
         $links[$menu_link_id] = array(
           'route_name' => isset($view_route_names[$view_id_display]) ? $view_route_names[$view_id_display] : "view.$view_id_display",
           // Identify URL embedded arguments and correlate them to a handler.
           'load arguments'  => array($this->view->storage->id(), $this->display['id'], '%index'),
-          'machine_name' => $menu_link_id,
+          'id' => $menu_link_id,
         );
         $links[$menu_link_id]['title'] = $menu['title'];
         $links[$menu_link_id]['description'] = $menu['description'];
@@ -323,6 +325,11 @@ public function executeHookMenuLinkDefaults(array &$existing_links) {
 
         // Insert item into the proper menu.
         $links[$menu_link_id]['menu_name'] = $menu['name'];
+        // Keep track of where we came from.
+        $links[$menu_link_id]['metadata'] = array(
+          'view_id' => $view_id,
+          'display_id' => $display_id,
+        );
       }
     }
 
diff --git a/core/modules/views/src/Tests/Plugin/DisplayPageTest.php b/core/modules/views/src/Tests/Plugin/DisplayPageTest.php
index 0067ce3..97e5cb9 100644
--- a/core/modules/views/src/Tests/Plugin/DisplayPageTest.php
+++ b/core/modules/views/src/Tests/Plugin/DisplayPageTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views\Tests\Plugin;
 
+use Drupal\Core\Session\AnonymousUserSession;
 use Drupal\views\Views;
 use Drupal\views\Tests\ViewUnitTestBase;
 use Symfony\Component\HttpFoundation\Request;
@@ -32,7 +33,7 @@ class DisplayPageTest extends ViewUnitTestBase {
    *
    * @var array
    */
-  public static $modules = array('system', 'user', 'menu_link', 'field', 'entity');
+  public static $modules = array('system', 'user', 'field', 'entity');
 
   /**
    * The router dumper to get all routes.
@@ -49,13 +50,13 @@ protected function setUp() {
 
     // Setup the needed tables in order to make the drupal router working.
     $this->installSchema('system', array('url_alias'));
-    $this->installSchema('menu_link', 'menu_links');
   }
 
   /**
    * Checks the behavior of the page for access denied/not found behaviors.
    */
   public function testPageResponses() {
+    \Drupal::currentUser()->setAccount(new AnonymousUserSession());
     $subrequest = Request::create('/test_page_display_403', 'GET');
     $response = $this->container->get('http_kernel')->handle($subrequest, HttpKernelInterface::SUB_REQUEST);
     $this->assertEqual($response->getStatusCode(), 403);
diff --git a/core/modules/views/src/Tests/Wizard/MenuTest.php b/core/modules/views/src/Tests/Wizard/MenuTest.php
index 3e9b8be..a245f57 100644
--- a/core/modules/views/src/Tests/Wizard/MenuTest.php
+++ b/core/modules/views/src/Tests/Wizard/MenuTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\views\Tests\Wizard;
 
+use Drupal\Component\Utility\String;
+
 /**
  * Tests the ability of the views wizard to put views in a menu.
  *
@@ -39,15 +41,14 @@ function testMenus() {
     $this->assertLinkByHref(url($view['page[path]']));
 
     // Make sure the link is associated with the main menu.
-    $links = menu_load_links('main');
-    $found = FALSE;
-    foreach ($links as $link) {
-      if ($link['link_path'] == $view['page[path]']) {
-        $found = TRUE;
-        break;
-      }
-    }
-    $this->assertTrue($found, t('Found a link to %path in the main menu', array('%path' => $view['page[path]'])));
+    /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
+    $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
+    /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
+    $link = $menu_link_manager->createInstance('views_view:views.' . $view['id'] . '.page_1');
+    $url = $link->getUrlObject();
+    $this->assertEqual($url->getRouteName(), 'view.' . $view['id'] . '.page_1', String::format('Found a link to %path in the main menu', array('%path' => $view['page[path]'])));
+    $metadata = $link->getMetaData();
+    $this->assertEqual(array('view_id' => $view['id'], 'display_id' => 'page_1'), $metadata);
   }
 
 }
diff --git a/core/modules/views/src/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php
index ad8e801..c7e810b 100644
--- a/core/modules/views/src/ViewExecutable.php
+++ b/core/modules/views/src/ViewExecutable.php
@@ -1509,17 +1509,16 @@ public function attachDisplays() {
   }
 
   /**
-   * Returns default menu links from the view and the named display handler.
+   * Returns menu links from the view and the named display handler.
    *
    * @param string $display_id
    *   A display ID.
-   * @param array $links
-   *   An array of default menu link items passed from
-   *   views_menu_link_defaults_alter().
    *
    * @return array|bool
+   *   The generated menu links for this view and display, FALSE if the call
+   *   to ::setDisplay failed.
    */
-  public function executeHookMenuLinkDefaults($display_id = NULL, &$links = array()) {
+  public function getMenuLinks($display_id = NULL) {
     // Prepare the view with the information we have. This was probably already
     // called, but it's good to be safe.
     if (!$this->setDisplay($display_id)) {
@@ -1528,7 +1527,7 @@ public function executeHookMenuLinkDefaults($display_id = NULL, &$links = array(
 
     // Execute the hook.
     if (isset($this->display_handler)) {
-      return $this->display_handler->executeHookMenuLinkDefaults($links);
+      return $this->display_handler->getMenuLinks();
     }
   }
 
diff --git a/core/modules/views/views.links.menu.yml b/core/modules/views/views.links.menu.yml
new file mode 100644
index 0000000..6188677
--- /dev/null
+++ b/core/modules/views/views.links.menu.yml
@@ -0,0 +1,4 @@
+views_view:
+  class: Drupal\views\Plugin\Menu\ViewsMenuLink
+  form_class: Drupal\views\Plugin\Menu\Form\ViewsMenuLinkForm
+  deriver: \Drupal\views\Plugin\Derivative\ViewsMenuLink
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index d9d9805..7ad98f5 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -305,23 +305,6 @@ function views_permission() {
 }
 
 /**
- * Implements hook_menu_link_defaults_alter().
- */
-function views_menu_link_defaults_alter(array &$links) {
-  // @todo Decide what to do with all the crazy logic in views_menu_alter() in
-  // https://drupal.org/node/2107533.
-  $views = Views::getApplicableViews('uses_hook_menu');
-  foreach ($views as $data) {
-    /** @var \Drupal\views\ViewExecutable $view */
-    list($view, $display_id) = $data;
-    $result = $view->executeHookMenuLinkDefaults($display_id, $links);
-    foreach ($result as $link_id => $link) {
-      $links[$link_id] = $link;
-    }
-  }
-}
-
-/**
  * Implements hook_page_alter().
  */
 function views_page_alter(&$page) {
diff --git a/core/profiles/standard/standard.info.yml b/core/profiles/standard/standard.info.yml
index 7029739..a1c07b2 100644
--- a/core/profiles/standard/standard.info.yml
+++ b/core/profiles/standard/standard.info.yml
@@ -14,6 +14,7 @@ dependencies:
   - comment
   - contextual
   - contact
+  - menu_link_content
   - datetime
   - block_content
   - quickedit
diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install
index 30656be..8c129fe 100644
--- a/core/profiles/standard/standard.install
+++ b/core/profiles/standard/standard.install
@@ -43,16 +43,11 @@ function standard_install() {
     ->fields(array('uid' => 1, 'rid' => 'administrator'))
     ->execute();
 
-  // Create a Home link in the main menu.
-  $menu_link = entity_create('menu_link', array(
-    'link_title' => t('Home'),
-    'link_path' => '<front>',
-    'menu_name' => 'main',
-  ));
-  $menu_link->save();
-
   // Enable the Contact link in the footer menu.
-  menu_link_maintain('contact', 'enable', 'contact');
+  /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
+  $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
+  $menu_link_manager->updateDefinition('contact.site_page', array('hidden' => 0));
+
   user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access site-wide contact form'));
   user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('access site-wide contact form'));
 
diff --git a/core/profiles/standard/standard.links.menu.yml b/core/profiles/standard/standard.links.menu.yml
new file mode 100644
index 0000000..2278b21
--- /dev/null
+++ b/core/profiles/standard/standard.links.menu.yml
@@ -0,0 +1,4 @@
+standard.front_page:
+  title: 'Home'
+  route_name: '<front>'
+  menu_name: main
diff --git a/core/tests/Drupal/Tests/Core/Menu/MenuLinkMock.php b/core/tests/Drupal/Tests/Core/Menu/MenuLinkMock.php
index 7086aa6..9a9e98e 100644
--- a/core/tests/Drupal/Tests/Core/Menu/MenuLinkMock.php
+++ b/core/tests/Drupal/Tests/Core/Menu/MenuLinkMock.php
@@ -28,7 +28,6 @@ class MenuLinkMock extends MenuLinkBase {
     'options' => array(),
     'expanded' => '0',
     'hidden' => '0',
-    'discovered' => '1',
     'provider' => 'simpletest',
     'metadata' => array(),
     'class' => 'Drupal\\Tests\\Core\Menu\\MenuLinkMock',
diff --git a/core/themes/bartik/bartik.theme b/core/themes/bartik/bartik.theme
index 836a8f7..27f8973 100644
--- a/core/themes/bartik/bartik.theme
+++ b/core/themes/bartik/bartik.theme
@@ -55,18 +55,15 @@ function bartik_preprocess_page(&$variables) {
   // Store back the classes to the htmlpage object.
   $attributes['class'] = $classes;
 
-  // Pass the main menu and secondary menu to the template as render arrays.
+  // Set additional attributes on the primary and secondary navigation menus.
   if (!empty($variables['main_menu'])) {
     $variables['main_menu']['#attributes']['id'] = 'main-menu-links';
-    $variables['main_menu']['#attributes']['class'] = array('links', 'clearfix');
+    $variables['main_menu']['#attributes']['class'][] = 'links';
   }
   if (!empty($variables['secondary_menu'])) {
     $variables['secondary_menu']['#attributes']['id'] = 'secondary-menu-links';
-    $variables['secondary_menu']['#attributes']['class'] = array(
-      'links',
-      'inline',
-      'clearfix',
-    );
+    $variables['secondary_menu']['#attributes']['class'][] = 'links';
+    $variables['secondary_menu']['#attributes']['class'][] = 'inline';
   }
 
   // Set the options that apply to both page and maintenance page.
@@ -138,10 +135,12 @@ function bartik_preprocess_block(&$variables) {
 }
 
 /**
- * Implements THEME_menu_tree().
+ * Implements hook_preprocess_HOOK() for menu-tree.html.twig.
+ *
+ * @see template_preprocess_menu_tree()
  */
-function bartik_menu_tree($variables) {
-  return '<ul class="menu clearfix">' . $variables['tree'] . '</ul>';
+function bartik_preprocess_menu_tree(&$variables) {
+  $variables['attributes']['class'][] = 'clearfix';
 }
 
 /**
diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme
index abf6c4f..e656626 100644
--- a/core/themes/seven/seven.theme
+++ b/core/themes/seven/seven.theme
@@ -148,7 +148,7 @@ function seven_preprocess_block_content_add_list(&$variables) {
 function seven_preprocess_admin_block_content(&$variables) {
   if (!empty($variables['content'])) {
     foreach ($variables['content'] as $key => $item) {
-      $variables['content'][$key]['url'] = url($item['link_path']);
+      $variables['content'][$key]['url'] = $item['url']->toString();
     }
   }
 }
