Index: admin_menu.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/admin_menu/admin_menu.js,v
retrieving revision 1.7.2.7.2.4
diff -u -p -r1.7.2.7.2.4 admin_menu.js
--- admin_menu.js	26 Mar 2009 23:11:23 -0000	1.7.2.7.2.4
+++ admin_menu.js	27 Mar 2009 22:32:58 -0000
@@ -1,20 +1,40 @@
 /* $Id: admin_menu.js,v 1.7.2.7.2.4 2009/03/26 23:11:23 sun Exp $ */
 
 Drupal.adminMenu = Drupal.adminMenu || {};
+Drupal.admin = Drupal.admin || { behaviors: {} };
 
 /**
  * Core behavior for Administration menu.
  *
- * This tests whether there is an administration menu is in the output and
- * executes all registered behaviors.
+ * Test whether there is an administration menu is in the output and execute all
+ * registered behaviors.
  */
 Drupal.behaviors.adminMenu = function (context) {
-  var $adminMenu = $('#admin-menu');
-  if ($adminMenu.size()) {
-    $.each(Drupal.adminMenu, function() {
-      this(context, $adminMenu);
+  // Check whether administration menu should be suppressed.
+  if (Drupal.settings.admin_menu && Drupal.settings.admin_menu.suppress) {
+    return;
+  }
+  var $adminMenu = $('#admin-menu:not(.admin-menu-processed)', context);
+  // Client-side caching; if administration menu is not in the output, try to
+  // fetch from the server and cache it in the browser.  If the menu is in the
+  // output already, this means there is a new version.
+  if (!$adminMenu.size() && Drupal.settings.admin_menu && Drupal.settings.admin_menu.hash) {
+    $.ajax({
+      cache: true,
+      dataType: 'html',
+      // @todo Clean URLs required for JS callback handler support.
+      url: Drupal.settings.basePath + 'index.php?q=js/admin_menu/cache/' + Drupal.settings.admin_menu.hash,
+      success: function(response, status) {
+        if (typeof response == 'string' && response.length > 0) {
+          $('body', context).prepend(response);
+        }
+        var $adminMenu = $('#admin-menu:not(.admin-menu-processed)', context);
+        Drupal.admin.attachBehaviors(context, $adminMenu);
+      }
     });
   }
+  // If administration menu is in the output, apply our behaviors.
+  Drupal.admin.attachBehaviors(context, $adminMenu);
 };
 
 /**
@@ -29,9 +49,21 @@ Drupal.behaviors.adminMenuCollapseModule
 };
 
 /**
+ * Attach administrative behaviors.
+ */
+Drupal.admin.attachBehaviors = function (context, $adminMenu) {
+  if ($adminMenu.size()) {
+    $adminMenu.addClass('admin-menu-processed');
+    $.each(Drupal.admin.behaviors, function() {
+      this(context, $adminMenu);
+    });
+  }
+};
+
+/**
  * Apply 'margin-top'; directly applying marginTop does not work in IE.
  */
-Drupal.adminMenu.marginTop = function (context, $adminMenu) {
+Drupal.admin.behaviors.marginTop = function (context, $adminMenu) {
   if (Drupal.settings.admin_menu && Drupal.settings.admin_menu.margin_top) {
     $('body', context).addClass('admin-menu');
   }
@@ -40,7 +72,7 @@ Drupal.adminMenu.marginTop = function (c
 /**
  * Apply 'position: fixed'.
  */
-Drupal.adminMenu.positionFixed = function (context, $adminMenu) {
+Drupal.admin.behaviors.positionFixed = function (context, $adminMenu) {
   if (Drupal.settings.admin_menu && Drupal.settings.admin_menu.position_fixed) {
     $adminMenu.css('position', 'fixed');
   }
@@ -49,7 +81,7 @@ Drupal.adminMenu.positionFixed = functio
 /**
  * Move page tabs into administration menu.
  */
-Drupal.adminMenu.pageTabs = function (context, $adminMenu) {
+Drupal.admin.behaviors.pageTabs = function (context, $adminMenu) {
   if (Drupal.settings.admin_menu && Drupal.settings.admin_menu.tweak_tabs) {
     $('ul.tabs.primary li', context).addClass('admin-menu-tab').appendTo('#admin-menu > ul');
     $('ul.tabs.secondary', context).appendTo('#admin-menu > ul > li.admin-menu-tab.active');
@@ -60,7 +92,7 @@ Drupal.adminMenu.pageTabs = function (co
 /**
  * Inject destination query strings for current page.
  */
-Drupal.adminMenu.destination = function (context, $adminMenu) {
+Drupal.admin.behaviors.destination = function (context, $adminMenu) {
   if (Drupal.settings.admin_menu && Drupal.settings.admin_menu.destination) {
     $('.admin-menu-destination', $adminMenu).each(function() {
       this.search += (!this.search.length ? '?' : '&') + Drupal.settings.admin_menu.destination;
@@ -74,7 +106,7 @@ Drupal.adminMenu.destination = function 
  * @todo This has to run last.  If another script registers additional behaviors
  *   it will not run last.
  */
-Drupal.adminMenu.hover = function (context, $adminMenu) {
+Drupal.admin.behaviors.hover = function (context, $adminMenu) {
   // Hover emulation for IE 6.
   if ($.browser.msie && parseInt(jQuery.browser.version) == 6) {
     $('li', $adminMenu).hover(function() {
Index: admin_menu.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/admin_menu/admin_menu.module,v
retrieving revision 1.43.2.17.2.2
diff -u -p -r1.43.2.17.2.2 admin_menu.module
--- admin_menu.module	8 Mar 2009 02:01:15 -0000	1.43.2.17.2.2
+++ admin_menu.module	28 Mar 2009 23:28:23 -0000
@@ -50,6 +50,13 @@ function admin_menu_theme() {
  * Implementation of hook_menu().
  */
 function admin_menu_menu() {
+  // AJAX callback.
+  $items['js/admin_menu/cache'] = array(
+    'page callback' => 'admin_menu_js_cache',
+    'access arguments' => array('access administration menu'),
+    'type' => MENU_CALLBACK,
+  );
+  // Module settings.
   $items['admin/settings/admin_menu'] = array(
     'title' => 'Administration menu',
     'description' => 'Adjust administration menu settings.',
@@ -58,6 +65,7 @@ function admin_menu_menu() {
     'access arguments' => array('administer site configuration'),
     'file' => 'admin_menu.inc',
   );
+  // Menu link callbacks.
   $items['admin_menu/toggle-modules'] = array(
     'page callback' => 'admin_menu_toggle_modules',
     'access arguments' => array('administer site configuration'),
@@ -88,21 +96,28 @@ function admin_menu_init() {
     // Performance: Defer execution.
     drupal_add_js($path . '/admin_menu.js', 'module', 'header', TRUE);
 
-    // The destination query string for the current page is applied via JS.
-    drupal_add_js(array('admin_menu' => array('destination' => drupal_get_destination())), 'setting');
+    // Destination query strings are applied via JS.
+    $settings['destination'] = drupal_get_destination();
+
+    // Hash for client-side HTTP/AJAX caching.
+    if (!empty($_COOKIE['has_js']) && !empty($GLOBALS['user']->admin_menu_hash)) {
+      $settings['hash'] = $GLOBALS['user']->admin_menu_hash;
+    }
 
     if ($setting = variable_get('admin_menu_margin_top', 1)) {
-      drupal_add_js(array('admin_menu' => array('margin_top' => $setting)), 'setting');
+      $settings['margin_top'] = $setting;
     }
     if ($setting = variable_get('admin_menu_position_fixed', 0)) {
-      drupal_add_js(array('admin_menu' => array('position_fixed' => $setting)), 'setting');
+      $settings['position_fixed'] = $setting;
     }
     if ($setting = variable_get('admin_menu_tweak_tabs', 0)) {
-      drupal_add_js(array('admin_menu' => array('tweak_tabs' => $setting)), 'setting');
+      $settings['tweak_tabs'] = $setting;
     }
     if ($_GET['q'] == 'admin/build/modules') {
-      drupal_add_js(array('admin_menu' => array('tweak_modules' => variable_get('admin_menu_tweak_modules', 0))), 'setting');
+      $settings['tweak_modules'] = variable_get('admin_menu_tweak_modules', 0);
     }
+
+    drupal_add_js(array('admin_menu' => $settings), 'setting');
   }
 }
 
@@ -120,8 +135,10 @@ function admin_menu_init() {
  */
 function admin_menu_suppress($set = TRUE) {
   static $suppress = FALSE;
-  if (!empty($set)) {
+  // drupal_add_js() must only be invoked once.
+  if (!empty($set) && $suppress === FALSE) {
     $suppress = TRUE;
+    drupal_add_js(array('admin_menu' => array('suppress' => 1)), 'setting');
   }
   return $suppress;
 }
@@ -139,34 +156,98 @@ function admin_menu_footer($main = 0) {
   }
   global $user, $language;
 
+  // Determine whether we need to rebuild.
+  $rebuild = variable_get('admin_menu_rebuild_links', FALSE);
+
+  // @todo
+  // Do nothing at all here if the client supports client-side caching, no
+  // rebuild is needed, the user has a hash, and is NOT requesting the cache
+  // update path.
+  if (!empty($_COOKIE['has_js']) && !$rebuild && !empty($user->admin_menu_hash) && strpos($_GET['q'], 'js/admin_menu/cache') !== 0) {
+    return;
+  }
+
   $cid = 'admin_menu:' . $user->uid . ':' . $language->language;
 
-  // Check for the flag indicating that we need to rebuild.
-  if (variable_get('admin_menu_rebuild_links', FALSE)) {
+  // Check for the flag indicating that we need to rebuild the menu.
+  if ($rebuild) {
     module_load_include('inc', 'admin_menu');
     _admin_menu_rebuild_links();
     variable_del('admin_menu_rebuild_links');
   }
-  // Try to load and output administration menu from cache.
+  // Try to load and output administration menu from server-side cache.
   else {
     $cache = cache_get($cid, 'cache_menu');
     if ($cache && isset($cache->data)) {
-      return $cache->data;
+      $content = $cache->data;
     }
   }
 
   // Rebuild the output.
-  $content  = '<div id="admin-menu">';
-  $content .= admin_menu_tree_output(menu_tree_all_data('admin_menu'));
-  $content .= '</div>';
+  if (!isset($content)) {
+    $content  = '<div id="admin-menu">';
+    $content .= admin_menu_tree_output(menu_tree_all_data('admin_menu'));
+    $content .= '</div>';
+  
+    // Cache the menu for this user.
+    cache_set($cid, $content, 'cache_menu');
+  }
 
-  // Cache the menu for this user.
-  cache_set($cid, $content, 'cache_menu');
+  // Store the new hash for this user.
+  if (!empty($_COOKIE['has_js'])) {
+    user_save($user, array('admin_menu_hash' => md5($content)));
+  }
 
   return $content;
 }
 
 /**
+ * Implementation of hook_js().
+ */
+function admin_menu_js() {
+  return array(
+    'cache' => array(
+      'callback' => 'admin_menu_js_cache',
+      'dependencies' => array('user'),
+    ),
+  );
+}
+
+/**
+ * Menu callback; Output administration menu for HTTP caching via AJAX request.
+ */
+function admin_menu_js_cache($hash = NULL) {
+  // Fetch the menu.
+  $content = admin_menu_footer();
+
+  // Determine if the client accepts gzipped data.
+  if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && function_exists('gzencode')) {
+    if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) {
+      $encoding = 'gzip';
+    }
+    elseif (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'x-gzip') !== FALSE) {
+      $encoding = 'x-gzip';
+    }
+    if (!empty($encoding)) {
+      header('Vary: Accept-Encoding');
+      header('Content-Encoding: ' . $encoding);
+      $content = gzencode($content, 9, FORCE_GZIP);
+    }
+  }
+
+  // @todo Find a proper cache lifetime.  Infinite?
+  header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 86400) . ' GMT');
+  header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
+  header('Cache-Control: max-age=' . 86400);
+  header('Content-Length: ' . strlen($content));
+
+  // Suppress Devel module.
+  $GLOBALS['devel_shutdown'] = FALSE;
+  echo $content;
+  exit;
+}
+
+/**
  * Return a rendered menu tree.
  *
  * @param $tree
