diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManager.php b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
index c14d1de1a3..6e1f0e8183 100644
--- a/core/lib/Drupal/Core/Menu/LocalTaskManager.php
+++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
@@ -194,91 +194,127 @@ public function getDefinitions() {
   /**
    * {@inheritdoc}
    */
-  public function getLocalTasksForRoute($route_name) {
-    if (!isset($this->instances[$route_name])) {
-      $this->instances[$route_name] = [];
-      if ($cache = $this->cacheBackend->get($this->cacheKey . ':' . $route_name)) {
-        $base_routes = $cache->data['base_routes'];
-        $parents = $cache->data['parents'];
-        $children = $cache->data['children'];
+  protected function findDefinitions() {
+    $definitions = parent::findDefinitions();
+
+    // do child parsing before being cached
+    foreach ($definitions as $id => &$definition) {
+      $parent_id = $definition['parent_id'];
+      // create children array on definition
+      if (!array_key_exists('children', $definition)) {
+        $definition['children'] = [];
       }
-      else {
-        $definitions = $this->getDefinitions();
-        // We build the hierarchy by finding all tabs that should
-        // appear on the current route.
-        $base_routes = [];
-        $parents = [];
-        $children = [];
-        foreach ($definitions as $plugin_id => $task_info) {
-          // Fill in the base_route from the parent to insure consistency.
-          if (!empty($task_info['parent_id']) && !empty($definitions[$task_info['parent_id']])) {
-            $task_info['base_route'] = $definitions[$task_info['parent_id']]['base_route'];
-            // Populate the definitions we use in the next loop. Using a
-            // reference like &$task_info causes bugs.
-            $definitions[$plugin_id]['base_route'] = $definitions[$task_info['parent_id']]['base_route'];
-          }
-          if ($route_name == $task_info['route_name']) {
-            if (!empty($task_info['base_route'])) {
-              $base_routes[$task_info['base_route']] = $task_info['base_route'];
-            }
-            // 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['parent_id'])) {
-              $parents[$task_info['parent_id']] = TRUE;
-            }
-          }
+      // add definition pointer to parent's child array
+      if(isset($definitions[$parent_id])) {
+        $definitions[$parent_id]['children'][$id] = &$definition;
+      }
+    }
+    return unserialize(serialize($definitions));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBreadcrumbsForRoute($route_name) {
+    $definitions = $this->getDefinitions();
+    $task_id = NULL;
+
+    $route_ids = array_keys($definitions);
+    $route_names = array_column($definitions, 'route_name');
+    foreach(array_keys($route_names, $route_name) as $key) {
+      $definition_id = $route_ids[$key];
+      $definition = $definitions[$definition_id];
+      $definition_parent_id = $definition['parent_id'];
+      // Set active current task id if it's a sub-tab of parent task tab.
+      if (!is_null($definition_parent_id) && array_key_exists($definition_parent_id, $definitions) && $route_name == $definitions[$definition_parent_id]['route_name']) {
+        $task_id = $definition_id;
+      }
+      // Find default catch-all route tab.
+      elseif (is_null($task_id)) {
+        $task_id = $definition_id;
+      }
+    }
+    // Start ancestors array with current non-NULL active task id gathered
+    // from route and work upward
+    $ancestors = [];
+    while (array_key_exists($task_id, $definitions)) {
+      if (!in_array($task_id, $ancestors)) {
+        $ancestors[] = $task_id;
+        $task_id = $definitions[$task_id]['parent_id'];
+      } else {
+        $task_id = NULL;
+      }
+    }
+    // Get breadcrumbs from furthest-up task_id.
+    return array_reverse($ancestors);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTaskTreeData($route_name) {
+    if ($cache = $this->cacheBackend->get($this->cacheKey . ':' . $route_name)) {
+      $data = $cache->data;
+    }
+    else {
+      $breadcrumbs = $this->getBreadcrumbsForRoute($route_name);
+      $definitions = $this->getDefinitions();
+      $task_tree = $definitions;
+      $top_level_task_id = current($breadcrumbs);
+      $base_route = $definitions[$top_level_task_id]['base_route'];
+      // unset all top-level links
+      foreach ($task_tree as $task_id => $task) {
+        if ($top_level_task_id !== $task_id && $task['base_route'] !== $base_route) {
+          unset($task_tree[$task_id]);
         }
-        if ($base_routes) {
-          // Find all the plugins with the same root and that are at the top
-          // level or that have a visible parent.
-          foreach ($definitions  as $plugin_id => $task_info) {
-            if (!empty($base_routes[$task_info['base_route']]) && (empty($task_info['parent_id']) || !empty($parents[$task_info['parent_id']]))) {
-              // Concat '> ' with root ID for the parent of top-level tabs.
-              $parent = empty($task_info['parent_id']) ? '> ' . $task_info['base_route'] : $task_info['parent_id'];
-              $children[$parent][$plugin_id] = $task_info;
-            }
+      }
+      // Remove unrelated children from tree.
+      $task_tree_pointer = &$task_tree;
+      foreach ($breadcrumbs as $level => $breadcrumb) {
+        foreach (array_keys($task_tree_pointer) as $task_tree_key) {
+          if ($task_tree_key !== $breadcrumb) {
+            $task_tree_pointer[$task_tree_key]['children'] = [];
           }
         }
-        $data = [
-          'base_routes' => $base_routes,
-          'parents' => $parents,
-          'children' => $children,
-        ];
-        $this->cacheBackend->set($this->cacheKey . ':' . $route_name, $data, Cache::PERMANENT, $this->cacheTags);
+        $task_tree_pointer = &$task_tree_pointer[$breadcrumb]['children'];
       }
-      // Create a plugin instance for each element of the hierarchy.
-      foreach ($base_routes as $base_route) {
-        // 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 = '> ' . $base_route;
-        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, the link generator 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;
-            }
+
+      $data = [
+        'tree' => $task_tree,
+        'breadcrumbs' => $breadcrumbs
+      ];
+      $this->cacheBackend->set($this->cacheKey . ':' . $route_name, $data, Cache::PERMANENT, $this->cacheTags);
+    }
+    return $data;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLocalTasksForRoute($route_name) {
+    if (!isset($this->instances[$route_name])) {
+      $this->instances[$route_name] = [];
+      //
+      $data = $this->getTaskTreeData($route_name);
+      $task_tree = $data['tree'];
+      $breadcrumbs = $data['breadcrumbs'];
+
+      // Convert the tree keyed by plugin IDs into a simple one with
+      // integer depth.  Create instances for each plugin along the way.
+      $task_tree_pointer = &$task_tree;
+      foreach ($breadcrumbs as $level => $breadcrumb) {
+        foreach ($task_tree_pointer as $plugin_id => $task_info) {
+          // Create a plugin instance for each element of the hierarchy.
+          $plugin = $this->createInstance($plugin_id);
+          if ($plugin_id == $breadcrumb) {
+            // This tab is active.
+            $plugin->setActive();
           }
-          $level++;
-        } while ($next_parent);
+          $this->instances[$route_name][$level][$plugin_id] = $plugin;
+        }
+        // Move down a level in the task tree
+        $task_tree_pointer = &$task_tree_pointer[$breadcrumb]['children'];
       }
-
     }
     return $this->instances[$route_name];
   }
diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php b/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php
index e9a748d514..cf0261538c 100644
--- a/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php
+++ b/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php
@@ -53,6 +53,26 @@ public function getLocalTasksForRoute($route_name);
   public function getTasksBuild($current_route_name, RefinableCacheableDependencyInterface &$cacheability);
 
   /**
+   * Return an array containing tree and breadcrumb data for the current route.
+   *
+   * @param string $route_name
+   *   The current route to retrieve route breadcrumb and tree data array.
+   * @return array
+   *   An array containing tree and breadcrumb data for the current route.
+   */
+  public function getTaskTreeData($route_name);
+
+  /**
+   * Return task breadcrumbs for the current route.
+   *
+   * @param string $route_name
+   *   The current route to find breadcrumbs.
+   * @return array
+   *   The task breadcrumb ids for the current route.
+   */
+  public function getBreadcrumbsForRoute($route_name);
+
+  /**
    * Collects the local tasks (tabs) for the current route.
    *
    * @param string $route_name
