Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.357
diff -u -p -r1.357 menu.inc
--- includes/menu.inc	17 Oct 2009 11:39:15 -0000	1.357
+++ includes/menu.inc	26 Oct 2009 01:36:11 -0000
@@ -1480,9 +1480,20 @@ function menu_get_custom_theme($initiali
   // Skip this if the site is offline or being installed or updated, since the
   // menu system may not be correctly initialized then.
   if ($initialize && !_menu_site_is_offline(TRUE) && (!defined('MAINTENANCE_MODE') || (MAINTENANCE_MODE != 'update' && MAINTENANCE_MODE != 'install'))) {
-    $router_item = menu_get_item();
-    if (!empty($router_item['access']) && !empty($router_item['theme_callback']) && function_exists($router_item['theme_callback'])) {
-      $custom_theme = call_user_func_array($router_item['theme_callback'], $router_item['theme_arguments']);
+    // First allow modules to dynamically set a custom theme for the current
+    // page. Since we can only have one, the last module to return a theme
+    // takes precedence.
+    $custom_themes = module_invoke_all('custom_theme');
+    if (!empty($custom_themes)) {
+      $custom_theme = array_pop($custom_themes);
+    }
+    // Otherwise, execute the theme callback function for the current page, if
+    // there is one, in order to determine the custom theme to set.
+    else {
+      $router_item = menu_get_item();
+      if (!empty($router_item['access']) && !empty($router_item['theme_callback']) && function_exists($router_item['theme_callback'])) {
+        $custom_theme = call_user_func_array($router_item['theme_callback'], $router_item['theme_arguments']);
+      }
     }
   }
   return $custom_theme;
Index: modules/simpletest/tests/menu.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/menu.test,v
retrieving revision 1.20
diff -u -p -r1.20 menu.test
--- modules/simpletest/tests/menu.test	17 Oct 2009 02:58:04 -0000	1.20
+++ modules/simpletest/tests/menu.test	26 Oct 2009 01:36:11 -0000
@@ -108,6 +108,29 @@ class MenuIncTestCase extends DrupalWebT
   }
 
   /**
+   * Test that the result of hook_custom_theme() overrides the theme callback.
+   */
+  function testHookCustomTheme() {
+    // Enable the Stark theme, which hook_custom_theme() will set as the custom
+    // theme for the requested page.
+    $admin_user = $this->drupalCreateUser(array('administer site configuration'));
+    $this->drupalLogin($admin_user);
+    $this->drupalPost('admin/appearance', array('status[stark]' => 1), t('Save configuration'));
+    $this->drupalLogout();
+
+    // Now save a variable that triggers our test implementation of
+    // hook_custom_theme() to run.
+    variable_set('menu_test_use_hook_custom_theme', TRUE);
+
+    // Request a page whose theme callback would normally cause the Seven theme
+    // to be used. Instead, we expect hook_custom_theme(), which sets Stark, to
+    // prevail.
+    $this->drupalGet('menu-test/theme-callback/use-admin-theme');
+    $this->assertText('Requested theme: stark. Actual theme: stark.', t('The result of hook_custom_theme() overrides what was set in a theme callback.'));
+    $this->assertRaw('stark/layout.css', t("The Stark theme's CSS appears on the page."));
+  }
+
+  /**
    * Tests for menu_link_maintain().
    */
   function testMenuLinkMaintain() {
Index: modules/simpletest/tests/menu_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/menu_test.module,v
retrieving revision 1.8
diff -u -p -r1.8 menu_test.module
--- modules/simpletest/tests/menu_test.module	30 Sep 2009 13:09:30 -0000	1.8
+++ modules/simpletest/tests/menu_test.module	26 Oct 2009 01:36:11 -0000
@@ -139,6 +139,21 @@ function menu_test_menu_name($new_name =
 }
 
 /**
+ * Implement hook_custom_theme().
+ *
+ * @return
+ *   The name of the custom theme to use for the current page.
+ */
+function menu_test_custom_theme() {
+  // Request the Stark theme, only if a variable has been set in the database
+  // to trigger this hook to do so. Otherwise, do not attempt to dynamically
+  // set the theme.
+  if (variable_get('menu_test_use_hook_custom_theme', FALSE)) {
+    return 'stark';
+  }
+}
+
+/**
  * Implement hook_menu_link_insert().
  *
  * @return
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.97
diff -u -p -r1.97 system.api.php
--- modules/system/system.api.php	24 Oct 2009 05:28:16 -0000	1.97
+++ modules/system/system.api.php	26 Oct 2009 01:36:11 -0000
@@ -1008,6 +1008,28 @@ function hook_theme_registry_alter(&$the
 }
 
 /**
+ * Return the machine-readable name of the theme to use for the current page.
+ *
+ * This hook can be used to dynamically set the theme for the custom page. It
+ * overrides the default theme as well as any per-page or per-section theme set
+ * by the theme callback function in hook_menu(). This should be used by
+ * modules which need to override the theme based on dynamic conditions.
+ *
+ * Since only one theme can be used at a time, the last (i.e., highest
+ * weighted) module which returns a theme name from this hook will prevail.
+ *
+ * @return
+ *   The machine-readable name of the theme that is requested for the current
+ *   page, if there is one.
+ */
+function hook_custom_theme() {
+  // Allow the user to request a particular theme via a query parameter.
+  if (isset($_GET['theme'])) {
+    return $_GET['theme'];
+  }
+}
+
+/**
  * Register XML-RPC callbacks.
  *
  * This hook lets a module register callback functions to be called when
