Index: includes/ajax.inc
===================================================================
--- includes/ajax.inc	(revision 28388)
+++ includes/ajax.inc	(working copy)
@@ -453,6 +453,9 @@
   $query_string = '?'. substr(variable_get('css_js_query_string', '0'), 0, 1);
 
   $scripts = drupal_add_js(NULL, NULL);
+  // Get replacements that are going to be made by contrib modules and take
+  // them into account so we don't double-load scripts.
+  $replacements = module_invoke_all('js_replacements');
   foreach ($scripts as $type => $data) {
     switch ($type) {
       case 'setting':
@@ -461,11 +464,17 @@
       case 'inline':
         // presently we ignore inline javascript.
         break;
+      case 'theme':
+        // Prevent JS from the wrong theme from loading.
+        // The theme JS should have already been loaded anyway.
+        break;
       default:
         // If JS preprocessing is off, we still need to output the scripts.
         // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones.
         foreach ($data as $path => $info) {
-          $js_files[] = base_path() . $path . ($info['cache'] ? $query_string : '?' . time());
+          // If the script is being replaced, take that replacment into account.
+          $final_path = isset($replacements[$type][$path]) ? $replacements[$type][$path] : $path;
+          $js_files[] = base_path() . $final_path . ($info['cache'] ? $query_string : '?' . time());
         }
     }
   }
@@ -477,6 +486,11 @@
     // If CSS preprocessing is off, we still need to output the styles.
     // Additionally, go through any remaining styles if CSS preprocessing is on and output the non-cached ones.
     foreach ($types as $type => $files) {
+      if ($type == 'theme') {
+        // Prevent CSS from the wrong theme from loading.
+        // The theme CSS should have already been loaded anyway.
+        continue;
+      }
       if ($type == 'module') {
         // Setup theme overrides for module styles.
         $theme_styles = array();
Index: ctools.module
===================================================================
--- ctools.module	(revision 28406)
+++ ctools.module	(working copy)
@@ -355,6 +355,29 @@
 }
 
 /**
+ * Implementation of hook_js_replacements().
+ * This is a hook that is not a standard yet. We hope jquery_update and others
+ * will expose this hook to inform modules which scripts they are modifying
+ * in the theme layer.
+ * The return format is $scripts[$type][$old_path] = $new_path.
+ */
+function ctools_js_replacements() {
+  $replacements = array();
+  // Until jquery_update is released with its own replacement hook, we will
+  // take those replacements into account here.
+  if (module_exists('jquery_update')) {
+    $replacements = array_merge_recursive($replacements, jquery_update_get_replacements());
+    foreach ($replacements as $type => $type_replacements) {
+      foreach ($type_replacements as $old_path => $new_filename) {
+        $replacements[$type][$old_path] = JQUERY_UPDATE_REPLACE_PATH . "/$new_filename";
+      }
+    }
+    $replacements['core']['misc/jquery.js'] = jquery_update_jquery_path();
+  }
+  return $replacements;
+}
+
+/**
  * Get a list of roles in the system.
  */
 function ctools_get_roles() {
