Index: includes/menu.inc
===================================================================
RCS file: /Users/Shared/code/drupal/includes/menu.inc,v
retrieving revision 1.310
diff -u -p -r1.310 menu.inc
--- includes/menu.inc	4 Jan 2009 20:04:32 -0000	1.310
+++ includes/menu.inc	13 Jan 2009 20:40:29 -0000
@@ -1422,6 +1422,14 @@ function menu_local_tasks($level = 0, $r
     if (!$router_item || !$router_item['access']) {
       return '';
     }
+    // Allow modules to add additional tabs, and to add tab properties to the
+    // router item to change the local task behavior.  By setting the
+    // tab_root of the router item, we allow related tabs to be gathered for
+    // a router path that is, for example, a MENU_CALLBACK.
+    $additional_items = module_invoke_all('menu_add_local_tasks');
+    if (isset($additional_items[$router_item['href']]['tab_root'])) {
+      $router_item = $additional_items[$router_item['href']] + $router_item;
+    }
     // Get all tabs and the root page.
     $result = db_select('menu_router', NULL, array('fetch' => PDO::FETCH_ASSOC))
       ->fields('menu_router')
@@ -1444,6 +1452,16 @@ function menu_local_tasks($level = 0, $r
       $tasks[$item['path']] = $item;
     }
 
+    foreach ($additional_items as $href => $tab) {
+      if (isset($tab['tab_parent']) && ($item = menu_get_item($href))) {
+        $item = $tab + $item;
+        $item['href'] = $href;
+        $children[$item['tab_parent']][$item['path']] = $item;
+        // Store the translated item for later use.
+        $tasks[$item['path']] = $item;
+      }
+    }
+
     // Find all tabs below the current path.
     $path = $router_item['path'];
     // Tab parenting may skip levels, so the number of parts in the path may not
Index: modules/menu/menu.api.php
===================================================================
RCS file: /Users/Shared/code/drupal/modules/menu/menu.api.php,v
retrieving revision 1.2
diff -u -p -r1.2 menu.api.php
--- modules/menu/menu.api.php	20 Dec 2008 18:24:37 -0000	1.2
+++ modules/menu/menu.api.php	13 Jan 2009 20:53:34 -0000
@@ -142,5 +142,27 @@ function hook_translated_menu_link_alter
 }
 
 /**
+ * Add local tasks on the fly.
+ *
+ * This hook may be used, for example to add a tab to nodes of a specific type
+ * or even a specific node.
+ *
+ * @return
+ *   An associative array where the key is the path we want the tab to point
+ *   to and the value is an array with two elements, tab_root and tab_parent.
+ */
+function hook_menu_add_local_tasks() {
+  // Show a dynamic tab on one specific node.
+  $nid = variable_get('menu_api_foo_nid', 0);
+  if (arg(0) == 'node' && arg(1) == $nid) {
+    // node/$nid/foo needs to be defined in hook_menu probably as a
+    // MENU_CALLBACK. node/$nid/foo is the path the tab points to
+    // and node/% is the tab parent and tab root.
+    $additional_local_tasks["node/$nid/foo"] = array('tab_root' => 'node/%', 'tab_parent' => 'node/%');
+    return $additional_local_tasks;
+  }
+}
+
+/**
  * @} End of "addtogroup hooks".
  */
Index: modules/simpletest/tests/menu.test
===================================================================
RCS file: /Users/Shared/code/drupal/modules/simpletest/tests/menu.test,v
retrieving revision 1.4
diff -u -p -r1.4 menu.test
--- modules/simpletest/tests/menu.test	28 Dec 2008 18:27:14 -0000	1.4
+++ modules/simpletest/tests/menu.test	13 Jan 2009 20:50:35 -0000
@@ -51,7 +51,7 @@ class MenuRebuildTestCase extends Drupal
       'group' => t('Menu'),
     );
   }
-  
+
   /**
    * Test if the 'menu_rebuild_needed' variable triggers a menu_rebuild() call.
    */
@@ -75,5 +75,42 @@ class MenuRebuildTestCase extends Drupal
     $admin_exists = db_result(db_query("SELECT path from {menu_router} WHERE path = 'admin'"));
     $this->assertEqual($admin_exists, 'admin', t("The menu has been rebuilt, the path 'admin' now exists again."));
   }
-  
+
+}
+
+class MenuAddLocalTaskTestCase extends DrupalWebTestCase {
+  function getInfo() {
+    return array(
+      'name' => t('Menu add local task test'),
+      'description' => t('Test adding a local task on the fly.'),
+      'group' => t('Menu'),
+    );
+  }
+
+  function setUp() {
+    // Enable dummy module that implements hook_menu.
+    parent::setUp('menu_test');
+  }
+
+  /**
+   * Test if we can add local tasks on the fly.
+   */
+  function testMenuAddLocalTask() {
+    // The dynamic tab only shows on a specific nid.
+    $node = $this->drupalCreateNode(array('type' => 'article'));
+    variable_set('menu_test_nid', $node->nid);
+    // Now we enable the rebuild variable and trigger menu_execute_active_handler()
+    // to rebuild the menu router.
+    variable_set('menu_rebuild_needed', TRUE);
+    $this->drupalGet("node/$node->nid");
+    $this->assertText(t('Menu test tab'));
+    $this->clickLink(t('Menu test tab'));
+    $this->assertText(t('Menu test page'));
+    $this->assertText(t('Menu test tab'));
+    $this->assertText(t('View'));
+    $node = $this->drupalCreateNode(array('type' => 'page'));
+    $this->drupalGet("node/$node->nid");
+    $this->assertNoText(t('Menu test tab'));
+    $this->assertNoText(t('View'));
+  }
 }
Index: modules/simpletest/tests/menu_test.module
===================================================================
RCS file: /Users/Shared/code/drupal/modules/simpletest/tests/menu_test.module,v
retrieving revision 1.1
diff -u -p -r1.1 menu_test.module
--- modules/simpletest/tests/menu_test.module	28 Dec 2008 18:27:14 -0000	1.1
+++ modules/simpletest/tests/menu_test.module	13 Jan 2009 20:51:57 -0000
@@ -6,15 +6,41 @@
  * Dummy module implementing hook menu to test changing the menu name.
  */
 
- /**
+/**
  * The name of the menu changes during the course of this test. Use a $_GET.
  */
 function menu_test_menu() {
 
   $items['menu_name_test'] = array(
-    'title' => t('Test menu_name router item'),
+    'title' => 'Test menu_name router item',
     'page callback' => 'node_save',
     'menu_name' => isset($_GET["hook_menu_name"]) ? $_GET["hook_menu_name"] : 'original',
   );
+  // We have pre-defined a 'special' nid.
+  $nid = variable_get('menu_test_nid', 0);
+  $items["node/$nid/menu_test"] =  array(
+    'title' => 'Menu test tab',
+    'page callback' => 'menu_test_page',
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
   return $items;
-}
\ No newline at end of file
+}
+
+/**
+ * Implementation of hook_menu_add_local_tasks().
+ */
+function menu_test_menu_add_local_tasks() {
+  $nid = variable_get('menu_test_nid', 0);
+  if (arg(0) == 'node' && arg(1) == $nid) {
+    // node/$nid/foo needs to be defined in hook_menu probably as a
+    // MENU_CALLBACK. node/$nid/foo is the path the tab points to
+    // and node/% is the tab parent and tab root.
+    $additional_local_tasks["node/$nid/menu_test"] = array('tab_root' => 'node/%', 'tab_parent' => 'node/%');
+    return $additional_local_tasks;
+  }
+}
+
+function menu_test_page() {
+  return t('Menu test page');
+}
