diff --git a/core/core.services.yml b/core/core.services.yml
index 99241e4..4d2e55d 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -169,6 +169,9 @@ services:
   plugin.manager.action:
     class: Drupal\Core\Action\ActionManager
     arguments: ['@container.namespaces']
+  plugin.manager.menu.local_task:
+    class: Drupal\Core\Menu\LocalTaskManager
+    arguments: ['@container.namespaces', '@controller_resolver', '@request', '@router.route_provider', '@module_handler']
   request:
     class: Symfony\Component\HttpFoundation\Request
     # @TODO the synthetic setting must be uncommented whenever drupal_session_initialize()
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 94d0e57..7404acb 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -1948,6 +1948,10 @@ function menu_local_tasks($level = 0) {
     if (!$router_item || !$router_item['access']) {
       return $empty;
     }
+    // @todo remove all code using {menu_router} and anything using MENU_*
+    // constants when all local actions and local tasks are converted to
+    // plugins. The remaining code should just invoke those managers plus do the
+    // invocations of hook_menu_local_tasks() and hook_menu_local_tasks_alter().
 
     // Get all tabs (also known as local tasks) and the root page.
     $result = db_select('menu_router', NULL, array('fetch' => PDO::FETCH_ASSOC))
@@ -2117,6 +2121,15 @@ function menu_local_tasks($level = 0) {
     // Remove the depth, we are interested only in their relative placement.
     $tabs = array_values($tabs);
     $data['tabs'] = $tabs;
+    // Look for route-based tabs.
+    $route_name = Drupal::request()->attributes->get('_route');
+    if (!empty($route_name)) {
+      $manager = Drupal::service('plugin.manager.menu.local_task');
+      $local_tasks = $manager->getTasksBuild($route_name);
+      foreach ($local_tasks as $level => $items) {
+        $data['tabs'][$level] = empty($data['tabs'][$level]) ? $items : array_merge($data['tabs'][$level], $items);
+      }
+    }
 
     // Allow modules to dynamically add further tasks.
     $module_handler = Drupal::moduleHandler();
@@ -2617,7 +2630,7 @@ function menu_get_active_breadcrumb() {
 
     // Don't show a link to the current page in the breadcrumb trail.
     $end = end($active_trail);
-    if ($item['href'] == $end['href']) {
+    if (Drupal::request()->attributes->get('system_path') == $end['href']) {
       array_pop($active_trail);
     }
 
diff --git a/core/lib/Drupal/Core/Annotation/Menu/LocalTask.php b/core/lib/Drupal/Core/Annotation/Menu/LocalTask.php
new file mode 100644
index 0000000..52ef6a1
--- /dev/null
+++ b/core/lib/Drupal/Core/Annotation/Menu/LocalTask.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Annotation\Menu\LocalTask.
+ */
+
+namespace Drupal\Core\Annotation\Menu;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a local task plugin annotation object.
+ *
+ * @Annotation
+ */
+class LocalTask extends Plugin {
+
+  /**
+   * The ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The static title for the local task.
+   *
+   * @ingroup plugin_translatable
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   */
+  public $title;
+
+  /**
+   * The route name.
+   *
+   * @var string
+   */
+  public $route_name;
+
+  /**
+   * The plugin ID of the root tab.
+   *
+   * @var array
+   */
+  public $tab_root_id;
+
+  /**
+   * The plugin ID of the parent tab (or NULL for a top-level tab).
+   *
+   * @var array|NULL
+   */
+  public $tab_parent_id;
+
+  /**
+   * The weight of the tab.
+   *
+   * @var int|NULL
+   */
+  public $weight;
+
+  /**
+   * The default link options.
+   *
+   * @var array (optional)
+   */
+  public $options = array();
+
+}
diff --git a/core/lib/Drupal/Core/Menu/LocalTaskBase.php b/core/lib/Drupal/Core/Menu/LocalTaskBase.php
new file mode 100644
index 0000000..7568d2b
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/LocalTaskBase.php
@@ -0,0 +1,155 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\LocalTaskBase.
+ */
+
+namespace Drupal\Core\Menu;
+
+use Drupal\Component\Plugin\PluginBase;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\StringTranslation\Translator\TranslatorInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+
+/**
+ * Provides defaults and base methods for menu local tasks plugins.
+ */
+abstract class LocalTaskBase extends PluginBase implements LocalTaskInterface, ContainerFactoryPluginInterface{
+
+  /**
+   * String translation object.
+   *
+   * @var \Drupal\Core\StringTranslation\Translator\TranslatorInterface
+   */
+  protected $t;
+
+  /**
+   * URL generator object.
+   *
+   * @var \Symfony\Component\Routing\Generator\UrlGeneratorInterface
+   */
+  protected $generator;
+
+  /**
+   * TRUE if this plugin is forced active for options attributes.
+   *
+   * @var bool
+   */
+  protected $active = FALSE;
+
+  /**
+   * Constructs a \Drupal\system\Plugin\LocalTaskBase object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param array $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\StringTranslation\Translator\TranslatorInterface $string_translation
+   *   The string translation object.
+   * @param \Symfony\Component\Routing\Generator\UrlGeneratorInterface $generator
+   *   The url generator object.
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition, TranslatorInterface $string_translation, UrlGeneratorInterface $generator) {
+    // This is available for subclasses that need to translate a dynamic title.
+    $this->t = $string_translation;
+    $this->generator = $generator;
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('string_translation'),
+      $container->get('url_generator')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteName() {
+    return $this->pluginDefinition['route_name'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTitle() {
+    // Subclasses may pull in the request or specific attributes as parameters.
+    return $this->pluginDefinition['title'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPath() {
+    // Subclasses may set a request into the generator or use any desired method
+    // to generate the path.
+    // @todo - use the new method from https://drupal.org/node/2031353
+    $path = $this->generator->generate($this->getRouteName());
+    // In order to get the Drupal path the base URL has to be stripped off.
+    $base_url = $this->generator->getContext()->getBaseUrl();
+    if (!empty($base_url) && strpos($path, $base_url) === 0) {
+      $path = substr($path, strlen($base_url));
+    }
+    return trim($path, '/');
+  }
+
+  /**
+   * Returns the weight of the local task.
+   *
+   * @return int
+   *   The weight of the task. If not defined in the annotation returns 0 by
+   *   default or -10 for the root tab.
+   */
+  public function getWeight() {
+    // By default the weight is 0, or -10 for the root tab.
+    if (!isset($this->pluginDefinition['weight'])) {
+      if ($this->pluginDefinition['tab_root_id'] == $this->pluginDefinition['id']) {
+        $this->pluginDefinition['weight'] = -10;
+      }
+      else {
+        $this->pluginDefinition['weight'] = 0;
+      }
+    }
+    return (int) $this->pluginDefinition['weight'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOptions() {
+    $options = $this->pluginDefinition['options'];
+    if ($this->active) {
+      if (empty($options['attributes']['class']) || !in_array('active', $options['attributes']['class'])) {
+        $options['attributes']['class'][] = 'active';
+      }
+    }
+    return (array) $options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setActive($active = TRUE) {
+    $this->active = $active;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getActive() {
+    return $this->active;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Menu/LocalTaskInterface.php b/core/lib/Drupal/Core/Menu/LocalTaskInterface.php
new file mode 100644
index 0000000..b7f581d
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/LocalTaskInterface.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Contains \DrupalCore\Menu\LocalTaskInterface.
+ */
+
+namespace Drupal\Core\Menu;
+
+/**
+ * Defines an interface for menu local tasks.
+ */
+interface LocalTaskInterface {
+
+  /**
+   * Get the route name from the settings.
+   *
+   * @return string
+   *   The name of the route this local task links to.
+   */
+  public function getRouteName();
+
+  /**
+   * Returns the localized title to be shown for this tab.
+   *
+   * Subclasses may add optional arguments like NodeInterface $node = NULL that
+   * will be supplied by the ControllerResolver.
+   *
+   * @return string
+   *   The title of the local task.
+   */
+  public function getTitle();
+
+  /**
+   * Returns an internal Drupal path to use when creating the link for the tab.
+   *
+   * Subclasses may add optional arguments like NodeInterface $node = NULL that
+   * will be supplied by the ControllerResolver.
+   *
+   * @return string
+   *   The path of this local task.
+   */
+  public function getPath();
+
+  /**
+   * Returns the weight of the local task.
+   *
+   * @return int|null
+   *   The weight of the task or NULL.
+   */
+  public function getWeight();
+
+  /**
+   * Returns an array of options suitable to pass to l().
+   *
+   * @return array
+   *   Associative array of options.
+   *
+   * @see l()
+   */
+  public function getOptions();
+
+  /**
+   * Sets the active status.
+   *
+   * @param bool $active
+   *   Sets whether this tab is active (e.g. a parent of the current tab).
+   *
+   * @return \Drupal\Core\Menu\LocalTaskInterface
+   *   The called object for chaining.
+   */
+  public function setActive($active = TRUE);
+
+  /**
+   * Gets the active status.
+   *
+   * @return bool
+   *   TRUE if the local task is active, FALSE otherwise.
+   *
+   * @see \Drupal\system\Plugin\MenuLocalTaskInterface::setActive()
+   */
+  public function getActive();
+
+}
diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManager.php b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
new file mode 100644
index 0000000..70db0b0
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
@@ -0,0 +1,227 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\MenuLocalTaskManager.
+ */
+
+namespace Drupal\Core\Menu;
+
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Routing\RouteProviderInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
+
+/**
+ * Manages discovery and instantiation of menu local task plugins.
+ *
+ * This manager finds plugins that are rendered as local tasks (usually tabs).
+ * Derivatives are supported for modules that wish to generate multiple tabs on
+ * behalf of something else.
+ */
+class LocalTaskManager extends DefaultPluginManager {
+
+  /**
+   * A controller resolver object.
+   *
+   * @var \Symfony\Component\HttpKernel\Controller\ControllerResolverInterface
+   */
+  protected $controllerResolver;
+
+  /**
+   * A request object.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
+   * The plugin instances.
+   *
+   * @var array
+   */
+  protected $instances = array();
+
+  /**
+   * The route provider to load routes by name.
+   *
+   * @var \Drupal\Core\Routing\RouteProviderInterface
+   */
+  protected $routeProvider;
+
+  /**
+   * Constructs a \Drupal\Core\Menu\LocalTaskManager object.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations,
+   * @param \Symfony\Component\HttpKernel\Controller\ControllerResolverInterface $controller_resolver
+   *   An object to use in introspecting route methods.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object to use for building titles and paths for plugin instances.
+   * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
+   *   The route provider to load routes by name.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.u
+   */
+  public function __construct(\Traversable $namespaces, ControllerResolverInterface $controller_resolver, Request $request, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler) {
+    parent::__construct('Menu\LocalTask', $namespaces, array(), 'Drupal\Core\Annotation\Menu\LocalTask');
+    $this->controllerResolver = $controller_resolver;
+    $this->request = $request;
+    $this->routeProvider = $route_provider;
+    $this->alterInfo($module_handler, 'local_tasks');
+  }
+
+  /**
+   * Gets the title for a local task.
+   *
+   * @param \Drupal\Core\Menu\LocalTaskInterface $local_task
+   *   A local task plugin instance to get the title for.
+   *
+   * @return string
+   *   The localized title.
+   */
+  public function getTitle(LocalTaskInterface $local_task) {
+    $controller = array($local_task, 'getTitle');
+    $arguments = $this->controllerResolver->getArguments($this->request, $controller);
+    return call_user_func_array($controller, $arguments);
+  }
+
+  /**
+   * Gets the Drupal path for a local task.
+   *
+   * @param \Drupal\Core\Menu\LocalTaskInterface $local_task
+   *   The local task plugin instance to get the path for.
+   *
+   * @return string
+   *   The path.
+   */
+  public function getPath(LocalTaskInterface $local_task) {
+    $controller = array($local_task, 'getPath');
+    $arguments = $this->controllerResolver->getArguments($this->request, $controller);
+    return call_user_func_array($controller, $arguments);
+  }
+
+  /**
+   * Find all local tasks that appear on a named route.
+   *
+   * @param string $route_name
+   *   The route for which to find local tasks.
+   *
+   * @return array
+   *   Returns an array of task levels. Each task level contains instances
+   *   of local tasks (LocalTaskInterface) which appear on the tab route.
+   *   The array keys are the depths and the values are arrays of plugin
+   *   instances.
+   */
+  public function getLocalTasksForRoute($route_name) {
+    if (!isset($this->instances[$route_name])) {
+      $this->instances[$route_name] = array();
+      // @todo - optimize this lookup by compiling or caching.
+      $definitions = $this->getDefinitions();
+      // We build the hierarchy by finding all tabs that should
+      // appear on the current route.
+      $tab_root_ids = array();
+      $parents = array();
+      foreach ($definitions as $plugin_id => $task_info) {
+        if ($route_name == $task_info['route_name']) {
+          $tab_root_ids[$task_info['tab_root_id']] = TRUE;
+          // Tabs that link to the current route are viable parents
+          // and their parent and children should be visible also.
+          // @todo - this only works for 2 levels of tabs.
+          // instead need to iterate up.
+          $parents[$plugin_id] = TRUE;
+          if (!empty($task_info['tab_parent_id'])) {
+            $parents[$task_info['tab_parent_id']] = TRUE;
+          }
+        }
+      }
+      if ($tab_root_ids) {
+        // Find all the plugins with the same root and that are at the top
+        // level or that have a visible parent.
+        $children = array();
+        foreach ($definitions  as $plugin_id => $task_info) {
+          if (!empty($tab_root_ids[$task_info['tab_root_id']]) && (empty($task_info['tab_parent_id']) || !empty($parents[$task_info['tab_parent_id']]))) {
+            // Concat '> ' with root ID for the parent of top-level tabs.
+            $parent = empty($task_info['tab_parent_id']) ? '> ' . $task_info['tab_root_id'] : $task_info['tab_parent_id'];
+            $children[$parent][$plugin_id] = $task_info;
+          }
+        }
+        foreach (array_keys($tab_root_ids) as $root_id) {
+          // Convert the tree keyed by plugin IDs into a simple one with
+          // integer depth.  Create instances for each plugin along the way.
+          $level = 0;
+          // We used this above as the top-level parent array key.
+          $next_parent = '> ' . $root_id;
+          do {
+            $parent = $next_parent;
+            $next_parent = FALSE;
+            foreach ($children[$parent] as $plugin_id => $task_info) {
+              $plugin = $this->createInstance($plugin_id);
+              $this->instances[$route_name][$level][$plugin_id] = $plugin;
+              // Normally, l() compares the href of every link with the current
+              // path and sets the active class accordingly. But the parents of
+              // the current local task may be on a different route in which
+              // case we have to set the class manually by flagging it active.
+              if (!empty($parents[$plugin_id]) && $route_name != $task_info['route_name']) {
+                $plugin->setActive();
+              }
+              if (isset($children[$plugin_id])) {
+                // This tab has visible children
+                $next_parent = $plugin_id;
+              }
+            }
+            $level++;
+          } while ($next_parent);
+        }
+      }
+    }
+    return $this->instances[$route_name];
+  }
+
+  /**
+   * Gets the render array for all local tasks.
+   *
+   * @param string $route_name
+   *   The route for which to make renderable local tasks.
+   *
+   * @return array
+   *   A render array as expected by theme_menu_local_tasks.
+   */
+  public function getTasksBuild($route_name) {
+    $tree = $this->getLocalTasksForRoute($route_name);
+    $build = array();
+    foreach ($tree as $level => $instances) {
+      foreach ($instances as $child) {
+        $path = $this->getPath($child);
+        // Find out whether the user has access to the task.
+        $route = $this->routeProvider->getRouteByName($child->getRouteName());
+        $map = array();
+        // @todo - replace this call when we have a real service for it.
+        $access = menu_item_route_access($route, $path, $map);
+        if ($access) {
+          // Need to flag the list element as active for a tab for the current
+          // route or if the plugin is set active (i.e. the parent tab).
+          $active = ($route_name == $child->getRouteName() || $child->getActive());
+          // @todo It might make sense to use menu link entities instead of
+          //   arrays.
+          $menu_link = array(
+            'title' => $this->getTitle($child),
+            'href' => $path,
+            'localized_options' => $child->getOptions(),
+          );
+          $build[$level][$path] = array(
+            '#theme' => 'menu_local_task',
+            '#link' => $menu_link,
+            '#active' => $active,
+            '#weight' => $child->getWeight(),
+            '#access' => $access,
+          );
+        }
+      }
+    }
+    return $build;
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/LocalTasksTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/LocalTasksTest.php
index 5f695c0..34034d6 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/LocalTasksTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/LocalTasksTest.php
@@ -140,4 +140,48 @@ protected function assertLocalTasks(array $hrefs, $level = 0) {
     }
   }
 
+  /**
+   * Tests the plugin based local tasks.
+   */
+  public function testPluginLocalTask() {
+    // Verify that local tasks appear as defined in the router.
+    $this->drupalGet('menu-local-task-test/tasks');
+
+    $this->drupalGet('menu-local-task-test/tasks/view');
+    $this->assertLocalTasks(array(
+      'menu-local-task-test/tasks/view',
+      'menu-local-task-test/tasks/settings',
+      'menu-local-task-test/tasks/edit',
+    ));
+
+    // Ensure the view tab is active.
+    $result = $this->xpath('//ul[contains(@class, "tabs")]//a[contains(@class, "active")]');
+    $this->assertEqual(1, count($result), 'There is just a single active tab.');
+    $this->assertEqual('View', (string) $result[0], 'The view tab is active.');
+
+    // Verify that local tasks in the second level appear.
+
+    $this->drupalGet('menu-local-task-test/tasks/settings');
+    $this->assertLocalTasks(array(
+      'menu-local-task-test/tasks/settings/sub1',
+      'menu-local-task-test/tasks/settings/sub2',
+    ), 1);
+
+    $result = $this->xpath('//ul[contains(@class, "tabs")]//a[contains(@class, "active")]');
+    $this->assertEqual(1, count($result), 'There is just a single active tab.');
+    $this->assertEqual('Settings', (string) $result[0], 'The settings tab is active.');
+
+
+    $this->drupalGet('menu-local-task-test/tasks/settings/sub1');
+    $this->assertLocalTasks(array(
+      'menu-local-task-test/tasks/settings/sub1',
+      'menu-local-task-test/tasks/settings/sub2',
+    ), 1);
+
+    $result = $this->xpath('//ul[contains(@class, "tabs")]//a[contains(@class, "active")]');
+    $this->assertEqual(2, count($result), 'There are tabs active on both levels.');
+    $this->assertEqual('Settings', (string) $result[0], 'The settings tab is active.');
+    $this->assertEqual('sub1', (string) $result[1], 'The sub1 tab is active.');
+  }
+
 }
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 5fef635..fd33136 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -939,6 +939,20 @@ function hook_menu_local_tasks_alter(&$data, $router_item, $root_path) {
 }
 
 /**
+ * Alter local tasks plugins.
+ *
+ * @param array $local_tasks
+ *   The array of local tasks plugin definitions, keyed by plugin ID.
+ *
+ * @see \Drupal\Core\Menu\LocalTaskInterface
+ * @see \Drupal\Core\Menu\LocalTaskManager
+ */
+function hook_local_task_alter(&$local_tasks) {
+  // Remove a specified local task plugin.
+  unset($local_tasks['example_plugin_id']);
+}
+
+/**
  * Alter links in the active trail before it is rendered as the breadcrumb.
  *
  * This hook is invoked by menu_get_active_breadcrumb() and allows alteration
diff --git a/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalTask/TestTasksEdit.php b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalTask/TestTasksEdit.php
new file mode 100644
index 0000000..40996c8
--- /dev/null
+++ b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalTask/TestTasksEdit.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_test\Plugin\Menu\LocalTask\TestTasksEdit.
+ */
+
+namespace Drupal\menu_test\Plugin\Menu\LocalTask;
+
+use Drupal\Core\Annotation\Menu\LocalTask;
+use Drupal\Core\Menu\LocalTaskBase;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @LocalTask(
+ *   id = "menu_local_task_test_tasks_edit",
+ *   route_name = "menu_local_task_test_tasks_edit",
+ *   title = @Translation("Edit"),
+ *   tab_root_id = "menu_local_task_test_tasks_view",
+ *   weight = "10"
+ * )
+ */
+class TestTasksEdit extends LocalTaskBase {
+
+}
diff --git a/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalTask/TestTasksSettings.php b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalTask/TestTasksSettings.php
new file mode 100644
index 0000000..67b2f65
--- /dev/null
+++ b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalTask/TestTasksSettings.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_test\Plugin\Menu\LocalTask\TestTasksSettings.
+ */
+
+namespace Drupal\menu_test\Plugin\Menu\LocalTask;
+
+use Drupal\Core\Annotation\Menu\LocalTask;
+use Drupal\Core\Menu\LocalTaskBase;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @LocalTask(
+ *   id = "menu_local_task_test_tasks_settings",
+ *   route_name = "menu_local_task_test_tasks_settings",
+ *   title = @Translation("Settings"),
+ *   tab_root_id = "menu_local_task_test_tasks_view"
+ * )
+ */
+class TestTasksSettings extends LocalTaskBase {
+
+}
diff --git a/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalTask/TestTasksSettingsSub1.php b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalTask/TestTasksSettingsSub1.php
new file mode 100644
index 0000000..2b0729a
--- /dev/null
+++ b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalTask/TestTasksSettingsSub1.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_test\Plugin\Menu\LocalTask\TestTasksSettingsSub1.
+ */
+
+namespace Drupal\menu_test\Plugin\Menu\LocalTask;
+
+use Drupal\Core\Annotation\Menu\LocalTask;
+use Drupal\Core\Menu\LocalTaskBase;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @LocalTask(
+ *   id = "menu_local_task_test_tasks_settings_sub1",
+ *   route_name = "menu_local_task_test_tasks_settings_sub1",
+ *   title = @Translation("sub1"),
+ *   tab_root_id = "menu_local_task_test_tasks_view",
+ *   tab_parent_id = "menu_local_task_test_tasks_settings",
+ *   weight = "-10"
+ * )
+ */
+class TestTasksSettingsSub1 extends LocalTaskBase {
+
+}
diff --git a/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalTask/TestTasksSettingsSub2.php b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalTask/TestTasksSettingsSub2.php
new file mode 100644
index 0000000..f3cedfe
--- /dev/null
+++ b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalTask/TestTasksSettingsSub2.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_test\Plugin\Menu\LocalTask\TestTasksSettingsSub2.
+ */
+
+namespace Drupal\menu_test\Plugin\Menu\LocalTask;
+
+use Drupal\Core\Annotation\Menu\LocalTask;
+use Drupal\Core\Menu\LocalTaskBase;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @LocalTask(
+ *   id = "menu_local_task_test_tasks_settings_sub2",
+ *   route_name = "menu_local_task_test_tasks_settings_sub2",
+ *   title = @Translation("sub2"),
+ *   tab_root_id = "menu_local_task_test_tasks_view",
+ *   tab_parent_id = "menu_local_task_test_tasks_settings"
+ * )
+ */
+class TestTasksSettingsSub2 extends LocalTaskBase {
+
+}
diff --git a/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalTask/TestTasksView.php b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalTask/TestTasksView.php
new file mode 100644
index 0000000..76bf0a3
--- /dev/null
+++ b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalTask/TestTasksView.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_test\Plugin\Menu\LocalTask\TestTasksView.
+ */
+
+namespace Drupal\menu_test\Plugin\Menu\LocalTask;
+
+use Drupal\Core\Menu\LocalTaskBase;
+use Drupal\Core\Annotation\Menu\LocalTask;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @LocalTask(
+ *   id = "menu_local_task_test_tasks_view",
+ *   route_name = "menu_local_task_test_tasks_view",
+ *   title = @Translation("View"),
+ *   tab_root_id = "menu_local_task_test_tasks_view"
+ * )
+ */
+class TestTasksView extends LocalTaskBase {
+
+}
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 f8792cc..508763c 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.module
+++ b/core/modules/system/tests/modules/menu_test/menu_test.module
@@ -441,6 +441,11 @@ function menu_test_menu() {
     'type' => MENU_LOCAL_ACTION,
   );
 
+  $items['menu-local-task-test/tasks'] = array(
+    'title' => 'Local tasks',
+    'route_name' => 'menu_local_task_test_tasks',
+  );
+
   return $items;
 }
 
diff --git a/core/modules/system/tests/modules/menu_test/menu_test.routing.yml b/core/modules/system/tests/modules/menu_test/menu_test.routing.yml
index 3154626..32e1ea2 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.routing.yml
+++ b/core/modules/system/tests/modules/menu_test/menu_test.routing.yml
@@ -44,3 +44,52 @@ menu_test_local_action4:
     _content: '\Drupal\menu_test\TestControllers::test2'
   requirements:
     _access: 'TRUE'
+
+menu_local_task_test_tasks:
+  pattern: '/menu-local-task-test/tasks'
+  defaults:
+    _content: '\Drupal\menu_test\TestControllers::test1'
+  requirements:
+    _access: 'TRUE'
+
+menu_local_task_test_tasks_tasks:
+  pattern: '/menu-local-task-test/tasks/tasks'
+  defaults:
+    _content: '\Drupal\menu_test\TestControllers::test1'
+  requirements:
+    _access: 'TRUE'
+
+menu_local_task_test_tasks_view:
+  pattern: '/menu-local-task-test/tasks/view'
+  defaults:
+    _content: '\Drupal\menu_test\TestControllers::test1'
+  requirements:
+    _access: 'TRUE'
+
+menu_local_task_test_tasks_edit:
+  pattern: '/menu-local-task-test/tasks/edit'
+  defaults:
+    _content: '\Drupal\menu_test\TestControllers::test1'
+  requirements:
+    _access: 'TRUE'
+
+menu_local_task_test_tasks_settings:
+  pattern: '/menu-local-task-test/tasks/settings'
+  defaults:
+    _content: '\Drupal\menu_test\TestControllers::test1'
+  requirements:
+    _access: 'TRUE'
+
+menu_local_task_test_tasks_settings_sub1:
+  pattern: '/menu-local-task-test/tasks/settings/sub1'
+  defaults:
+    _content: '\Drupal\menu_test\TestControllers::test1'
+  requirements:
+    _access: 'TRUE'
+
+menu_local_task_test_tasks_settings_sub2:
+  pattern: '/menu-local-task-test/tasks/settings/sub2'
+  defaults:
+    _content: '\Drupal\menu_test\TestControllers::test1'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/Plugin/Menu/LocalTask/ViewsListTask.php b/core/modules/views_ui/lib/Drupal/views_ui/Plugin/Menu/LocalTask/ViewsListTask.php
new file mode 100644
index 0000000..f1237fc
--- /dev/null
+++ b/core/modules/views_ui/lib/Drupal/views_ui/Plugin/Menu/LocalTask/ViewsListTask.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views_ui\Plugin\Menu\LocalTask\ViewsListTask.
+ */
+
+namespace Drupal\views_ui\Plugin\Menu\LocalTask;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Menu\LocalTaskBase;
+use Drupal\Core\Annotation\Menu\LocalTask;
+
+
+/**
+ * @LocalTask(
+ *   id = "views_ui_list_tab",
+ *   route_name = "views_ui.list",
+ *   title = @Translation("List"),
+ *   tab_root_id = "views_ui_list_tab"
+ * )
+ */
+class ViewsListTask extends LocalTaskBase {
+
+}
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/Plugin/Menu/LocalTask/ViewsSettingsAdvancedTask.php b/core/modules/views_ui/lib/Drupal/views_ui/Plugin/Menu/LocalTask/ViewsSettingsAdvancedTask.php
new file mode 100644
index 0000000..4ab9b77
--- /dev/null
+++ b/core/modules/views_ui/lib/Drupal/views_ui/Plugin/Menu/LocalTask/ViewsSettingsAdvancedTask.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views_ui\Plugin\Menu\LocalTask\ViewsSettingsAdvancedTas.
+ */
+
+namespace Drupal\views_ui\Plugin\Menu\LocalTask;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Menu\LocalTaskBase;
+use Drupal\Core\Annotation\Menu\LocalTask;
+
+/**
+ * @LocalTask(
+ *   id = "views_ui_settings_advanced_tab",
+ *   route_name = "views_ui.settings.advanced",
+ *   title = @Translation("Advanced"),
+ *   tab_root_id = "views_ui_list_tab",
+ *   tab_parent_id = "views_ui_settings_tab",
+ *   weight = "10"
+ * )
+ */
+class ViewsSettingsAdvancedTask extends LocalTaskBase {
+
+}
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/Plugin/Menu/LocalTask/ViewsSettingsBasicTask.php b/core/modules/views_ui/lib/Drupal/views_ui/Plugin/Menu/LocalTask/ViewsSettingsBasicTask.php
new file mode 100644
index 0000000..c8d454f
--- /dev/null
+++ b/core/modules/views_ui/lib/Drupal/views_ui/Plugin/Menu/LocalTask/ViewsSettingsBasicTask.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views_ui\Plugin\Menu\LocalTask\ViewsSettingsBasicTask.
+ */
+
+namespace Drupal\views_ui\Plugin\Menu\LocalTask;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Menu\LocalTaskBase;
+use Drupal\Core\Annotation\Menu\LocalTask;
+
+/**
+ * @LocalTask(
+ *   id = "views_ui_settings_basic_tab",
+ *   route_name = "views_ui.settings.basic",
+ *   title = @Translation("Basic"),
+ *   tab_root_id = "views_ui_list_tab",
+ *   tab_parent_id = "views_ui_settings_tab"
+ * )
+ */
+class ViewsSettingsBasicTask extends LocalTaskBase {
+
+}
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/Plugin/Menu/LocalTask/ViewsSettingsTask.php b/core/modules/views_ui/lib/Drupal/views_ui/Plugin/Menu/LocalTask/ViewsSettingsTask.php
new file mode 100644
index 0000000..9dddf80
--- /dev/null
+++ b/core/modules/views_ui/lib/Drupal/views_ui/Plugin/Menu/LocalTask/ViewsSettingsTask.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\views_ui\Plugin\Menu\LocalTask\ViewsSettingsTask.
+ */
+
+namespace Drupal\views_ui\Plugin\Menu\LocalTask;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Menu\LocalTaskBase;
+use Drupal\Core\Annotation\Menu\LocalTask;
+
+/**
+ * @LocalTask(
+ *   id = "views_ui_settings_tab",
+ *   route_name = "views_ui.settings.basic",
+ *   title = @Translation("Settings"),
+ *   tab_root_id = "views_ui_list_tab"
+ * )
+ */
+class ViewsSettingsTask extends LocalTaskBase {
+
+}
diff --git a/core/modules/views_ui/views_ui.module b/core/modules/views_ui/views_ui.module
index 4ce0595..628d0f9 100644
--- a/core/modules/views_ui/views_ui.module
+++ b/core/modules/views_ui/views_ui.module
@@ -25,29 +25,23 @@ function views_ui_menu() {
     'route_name' => 'views_ui.list',
   );
 
-  $items['admin/structure/views/list'] = array(
-    'title' => 'List',
-    'type' => MENU_DEFAULT_LOCAL_TASK,
-  );
-
   $items['admin/structure/views/add'] = array(
     'route_name' => 'views_ui.add',
     'type' => MENU_SIBLING_LOCAL_TASK,
   );
 
+  // @todo - remove all items of type MENU_VISIBLE_IN_BREADCRUMB
+  // when there is a route-aware breadcrumb builder.
   $items['admin/structure/views/settings'] = array(
     'title' => 'Settings',
     'route_name' => 'views_ui.settings.basic',
-    'type' => MENU_LOCAL_TASK,
-  );
-  $items['admin/structure/views/settings/basic'] = array(
-    'title' => 'Basic',
-    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'type' => MENU_VISIBLE_IN_BREADCRUMB,
   );
+
   $items['admin/structure/views/settings/advanced'] = array(
     'title' => 'Advanced',
     'route_name' => 'views_ui.settings.advanced',
-    'type' => MENU_LOCAL_TASK,
+    'type' => MENU_VISIBLE_IN_BREADCRUMB,
   );
 
   // The primary Edit View page. Secondary tabs for each Display are added in
diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php
new file mode 100644
index 0000000..5d61a9c
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php
@@ -0,0 +1,207 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Menu\LocalTaskManagerTest.
+ */
+
+namespace Drupal\Tests\Core\Menu;
+
+use Drupal\Component\Plugin\Factory\DefaultFactory;
+use Drupal\system\Plugin\Type\MenuLocalTaskManager;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Zend\Stdlib\ArrayObject;
+
+/**
+ * Tests local tasks manager.
+ *
+ * @see \Drupal\system\Plugin\Type\MenuLocalTaskManager
+ */
+class LocalTaskManagerTest extends UnitTestCase {
+
+  /**
+   * The tested manager.
+   *
+   * @var \Drupal\system\Plugin\Type\MenuLocalTaskManager
+   */
+  protected $manager;
+
+  /**
+   * The mocked controller resolver.
+   *
+   * @var \PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $controllerResolver;
+
+  /**
+   * The test request.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
+   * The mocked route provider.
+   *
+   * @var \PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $routeProvider;
+
+  /**
+   * The mocked plugin discovery.
+   *
+   * @var \PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $pluginDiscovery;
+
+  /**
+   * The plugin factory used in the test.
+   *
+   * @var \PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $factory;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Local tasks manager.',
+      'description' => 'Tests local tasks manager.',
+      'group' => 'Menu',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->controllerResolver = $this->getMock('Symfony\Component\HttpKernel\Controller\ControllerResolverInterface');
+    $this->request = new Request();
+    $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
+    $this->pluginDiscovery = $this->getMock('Drupal\Component\Plugin\Discovery\DiscoveryInterface');
+    $this->factory = $this->getMock('Drupal\Component\Plugin\Factory\FactoryInterface');
+
+    $this->setupLocalTaskManager();
+  }
+
+  /**
+   * Tests the getLocalTasksForRoute method.
+   *
+   * @see \Drupal\system\Plugin\Type\MenuLocalTaskManager::getLocalTasksForRoute()
+   */
+  public function testGetLocalTasksForRouteSingleLevelTitle() {
+    $definitions = array();
+    $definitions['menu_local_task_test_tasks_settings'] = array(
+      'id' => 'menu_local_task_test_tasks_settings',
+      'route_name' => 'menu_local_task_test_tasks_settings',
+      'title' => 'Settings',
+      'tab_root_id' => 'menu_local_task_test_tasks_view',
+      'class' => 'Drupal\menu_test\Plugin\Menu\MenuLocalTasksTestTasksSettings',
+    );
+    $definitions['menu_local_task_test_tasks_edit'] = array(
+      'id' => 'menu_local_task_test_tasks_edit',
+      'route_name' => 'menu_local_task_test_tasks_edit',
+      'title' => 'Settings',
+      'tab_root_id' => 'menu_local_task_test_tasks_view',
+      'class' => 'Drupal\menu_test\Plugin\Menu\MenuLocalTasksTestTasksEdit',
+      'weight' => 20,
+    );
+    $definitions['menu_local_task_test_tasks_view'] = array(
+      'id' => 'menu_local_task_test_tasks_view',
+      'route_name' => 'menu_local_task_test_tasks_view',
+      'title' => 'Settings',
+      'tab_root_id' => 'menu_local_task_test_tasks_view',
+      'class' => 'Drupal\menu_test\Plugin\Menu\MenuLocalTasksTestTasksView',
+    );
+
+    $this->pluginDiscovery->expects($this->any())
+      ->method('getDefinitions')
+      ->will($this->returnValue($definitions));
+
+    $mock_plugin = $this->getMock('Drupal\Core\Menu\LocalTaskInterface');
+
+    $map = array(
+      array('menu_local_task_test_tasks_settings', array(), $mock_plugin),
+      array('menu_local_task_test_tasks_edit', array(), $mock_plugin),
+      array('menu_local_task_test_tasks_view', array(), $mock_plugin),
+    );
+    $this->factory->expects($this->any())
+      ->method('createInstance')
+      ->will($this->returnValueMap($map));
+
+    $this->setupLocalTaskManager();
+
+    $local_tasks = $this->manager->getLocalTasksForRoute('menu_local_task_test_tasks_view');
+    $this->assertEquals(array(0 => array(
+      'menu_local_task_test_tasks_settings' => $mock_plugin,
+      'menu_local_task_test_tasks_view' => $mock_plugin,
+      'menu_local_task_test_tasks_edit' => $mock_plugin,
+    )), $local_tasks);
+  }
+
+  /**
+   * Tests the getTitle method.
+   *
+   * @see \Drupal\system\Plugin\Type\MenuLocalTaskManager::getTitle()
+   */
+  public function testGetTitle() {
+    $menu_local_task = $this->getMock('Drupal\Core\Menu\LocalTaskInterface');
+    $menu_local_task->expects($this->once())
+      ->method('getTitle');
+
+    $this->controllerResolver->expects($this->once())
+      ->method('getArguments')
+      ->with($this->request, array($menu_local_task, 'getTitle'))
+      ->will($this->returnValue(array()));
+
+    $this->manager->getTitle($menu_local_task);
+  }
+
+  /**
+   * Tests the getPath method.
+   *
+   * @see \Drupal\system\Plugin\Type\MenuLocalTaskManager::getPath()
+   */
+  public function testGetPath() {
+    $menu_local_task = $this->getMock('Drupal\Core\Menu\LocalTaskInterface');
+    $menu_local_task->expects($this->once())
+      ->method('getPath');
+
+    $this->controllerResolver->expects($this->once())
+      ->method('getArguments')
+      ->with($this->request, array($menu_local_task, 'getPath'))
+      ->will($this->returnValue(array()));
+
+    $this->manager->getPath($menu_local_task);
+  }
+
+  /**
+   * Setups the local task manager for the test.
+   */
+  protected function setupLocalTaskManager() {
+    $this->manager = $this
+      ->getMockBuilder('Drupal\Core\Menu\LocalTaskManager')
+      ->disableOriginalConstructor()
+      ->setMethods(NULL)
+      ->getMock();
+
+    $property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'controllerResolver');
+    $property->setAccessible(TRUE);
+    $property->setValue($this->manager, $this->controllerResolver);
+
+    $property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'request');
+    $property->setAccessible(TRUE);
+    $property->setValue($this->manager, $this->request);
+
+    $property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'discovery');
+    $property->setAccessible(TRUE);
+    $property->setValue($this->manager, $this->pluginDiscovery);
+
+    $property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'factory');
+    $property->setAccessible(TRUE);
+    $property->setValue($this->manager, $this->factory);
+  }
+
+}
+
