Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.911
diff -u -r1.911 common.inc
--- includes/common.inc	24 May 2009 17:39:30 -0000	1.911
+++ includes/common.inc	25 May 2009 21:29:56 -0000
@@ -2781,6 +2781,94 @@
 }
 
 /**
+ * Adds multiple JavaScript files and CSS files for a JavaScript plugin.
+ *
+ * @param $plugin
+ *   The name of the plugin to add.
+ * @param ...
+ *   Additional arguments to pass to the call to hook_js_plugin_add().
+ * @return
+ *   The results from invoking hook_js_plugin_add().
+ * @see hook_js_plugin()
+ * @see hook_js_plugin_add()
+ * @see hook_js_plugin_alter()
+ */
+function drupal_add_plugin($plugin) {
+  $plugin = drupal_get_plugin($plugin);
+  if (!isset($plugin)) {
+    return NULL;
+  }
+
+  // Add all the JavaScript, stylesheets and dependent plugins.
+  foreach (array('js', 'css', 'plugin') as $type) {
+    if (isset($plugin[$type]) && is_array($plugin[$type])) {
+      foreach ($plugin[$type] as $data => $options) {
+        // If the value is not an array, it's a filename or plugin name and
+        // passed as the first (and only) argument.
+        if (!is_array($options)) {
+          $data = $options;
+          $options = NULL;
+        }
+        // When drupal_add_js with 'type' => 'setting' is called, the first
+        // parameter ($data) is an array. Arrays can't be keys in PHP, so we
+        // have to get $data from the value array.
+        if (is_numeric($data)) {
+          $data = $options['data'];
+          unset($options['data']);
+        }
+        call_user_func('drupal_add_' . $type, $data, $options);
+      }
+    }
+  }
+
+  // Invoke hook_js_plugin_add() with extra parameters to allow any additional
+  // action.
+  $args = func_get_args();
+  array_unshift($args, 'js_plugin_add');
+  return call_user_func_array('module_invoke_all', $args);
+}
+
+/**
+ * Retrieves information from the JavaScript plugin registry.
+ *
+ * @param $plugin
+ *   If given, will provide the JavaScript plugin information for just the given
+ *   plugin name.
+ * @return
+ *   Returns all available JavaScript plugins, or the requested one in $plugin.
+ *   Otherwise, returns NULL.
+ * @see drupal_add_plugin()
+ */
+function drupal_get_plugin($plugin = NULL) {
+  $plugins = &drupal_static(__FUNCTION__);
+
+  if (!isset($plugins)) {
+    if ($cache = cache_get('js_plugin')) {
+      $plugins = $cache->data;
+    }
+    else {
+      // Invoke hook_js_plugin() to create the plugin registry.
+      $plugins = module_invoke_all('js_plugin');
+
+      // Invoke hook_js_plugin_alter() to allow modification of the
+      // registry. This is helpful to allow modules to update any plugins
+      // to later versions.
+      drupal_alter('js_plugin', $plugins);
+
+      // Save the registry to the cache.
+      cache_set('js_plugin', $plugins);
+    }
+  }
+
+  if (isset($plugin)) {
+    return isset($plugins[$plugin]) ? $plugins[$plugin] : NULL;
+  }
+  else {
+    return $plugins;
+  }
+}
+
+/**
  * Constructs an array of the defaults that are used for JavaScript items.
  *
  * @param $data
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.337
diff -u -r1.337 form.inc
--- includes/form.inc	24 May 2009 17:39:30 -0000	1.337
+++ includes/form.inc	25 May 2009 21:30:05 -0000
@@ -1992,7 +1992,7 @@
   // Adding the same javascript settings twice will cause a recursion error,
   // we avoid the problem by checking if the javascript has already been added.
   if ((isset($element['#ahah']['callback']) || isset($element['#ahah']['path'])) && isset($element['#ahah']['event']) && !isset($js_added[$element['#id']])) {
-    drupal_add_js('misc/jquery.form.js', array('weight' => JS_LIBRARY));
+    drupal_add_plugin('form');
     drupal_add_js('misc/ahah.js');
 
     $ahah_binding = array(
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.331
diff -u -r1.331 system.install
--- modules/system/system.install	25 May 2009 13:42:56 -0000	1.331
+++ modules/system/system.install	25 May 2009 21:30:27 -0000
@@ -293,6 +293,43 @@
     }
   }
 
+  // Make sure that all files from the JavaScript plugins are available.
+  if ($phase == 'runtime') {
+    // Force a refresh of the plugin repository.
+    cache_clear_all('js_plugin', 'cache');
+    drupal_static_reset('drupal_get_plugin');
+    $plugins = drupal_get_plugin();
+    foreach ($plugins as $name => $plugin) {
+      foreach (array('js', 'css') as $type) {
+        if (isset($plugin[$type]) && is_array($plugin[$type])) {
+          foreach ($plugin[$type] as $data => $options) {
+            // Construct the data and options properties properly. 
+            if (!is_array($options)) {
+              $data = $options;
+              $options = array();
+            }
+            $options += array('type' => 'file', 'data' => $data);
+            if ($options['type'] == 'file' && !file_exists($options['data'])) {
+              $requirements['plugin ' . $name] = array(
+                'title' => $plugin['title'],
+                'value' => $t('Files missing'),
+                'severity' => REQUIREMENT_ERROR,
+                'description' => $t('Files from <a href="@project_page">!title</a> were not found. You must <a href="@download">download and install %version</a> to the <em>sites/all/plugins</em>.', array(
+                  '@project_page' => $plugin['project_page'],
+                  '!title' => $plugin['title'],
+                  '@download' => $plugin['download'],
+                  '%version' => $plugin['version'],
+                  '%file' => $options['data'],
+                )),
+              );
+              break;
+            }
+          }
+        }
+      }
+    }
+  }
+
   return $requirements;
 }
 
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.38
diff -u -r1.38 system.api.php
--- modules/system/system.api.php	24 May 2009 17:39:34 -0000	1.38
+++ modules/system/system.api.php	25 May 2009 21:30:15 -0000
@@ -240,6 +240,85 @@
 }
 
 /**
+ * Registers any JavaScript plugins associated with the module.
+ *
+ * @return
+ *   An array defining any JavaScript plugins associated with the module.
+ * @see system_js_plugin()
+ */
+function hook_js_plugin() {
+  // jCarousel.
+  $plugins['jcarousel'] = array(
+    'title' => 'jCarousel',
+    'project_page' => 'http://sorgalla.com/jcarousel/',
+    'version' => 0.23,
+    'download' => 'http://sorgalla.com/projects/download.php?jcarousel',
+    'js' => array(
+      // Items that are just strings are interpreted as stright files. 
+      drupal_get_path('module', 'jcarousel') . '/jquery.jcarousel.js',
+    ),
+    'css' => array(
+      // By providing an array, it becomes the $options parameter during the
+      // drupal_add_js/drupal_add_css call.
+      drupal_get_path('module', 'jcarousel') . '/jquery.jcarousel.css' => array(
+        'type' => 'file',
+        'preprocess' => FALSE,
+        'media' => 'screen',
+      ),
+    ),
+    'plugin' => array(
+      // Defines a list of plugins that will also be added with this plugin.
+      'dependentplugin',
+    ),
+  );
+  return $plugins;
+}
+
+/**
+ * Alters the JavaScript plugin registry.
+ *
+ * @param $plugins
+ *   The JavaScript plugin registry.
+ * @return
+ *   An array with the updated JavaScript plugin registry.
+ * @see hook_js_plugin()
+ */
+function hook_js_plugin_alter(&$plugins) {
+  // Update the jCarousel plugin to version 1.0.
+  if (isset($plugins['jcarousel'])) {
+    if ($plugins['jcarousel']['version'] < 1.0) {
+      $plugins['jcarousel']['version'] = 1.0;
+      $plugins['jcarousel']['js'] = drupal_get_path('module', 'jcarousel_update') . '/jquery.jcarousel.js';
+      $plugins['jcarousel']['css'] = drupal_get_path('module', 'jcarousel_update') . '/jquery.jcarousel.css';
+    }
+  }
+  return $plugins;
+}
+
+/**
+ * A JavaScript plugin is being added to the page.
+ *
+ * This is helpful when special actions are to be taken when the plugin is
+ * added. Some plugins, for example, require special CSS files, or settings
+ * to be added to the page.
+ *
+ * @param $plugin
+ *   The JavaScript plugin being added.
+ * @param ...
+ *   Any additional arguments passed in when calling drupal_add_plugin().
+ * @see drupal_add_plugin()
+ */
+function hook_js_plugin_add($plugin) {
+  if ($plugin == 'jcarousel') {
+    // Add our JavaScript which registers the Drupal behaviors.
+    drupal_add_js(drupal_get_path('module', 'jcarousel') . '/jcarousel.js');
+
+    // Add the settings so that the Drupal behaviors act on them.
+    drupal_add_js($options['args'], 'setting'); 
+  }
+}
+
+/**
  * Perform alterations before a form is rendered.
  *
  * One popular use of this hook is to add form elements to the node form. When
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.699
diff -u -r1.699 system.module
--- modules/system/system.module	25 May 2009 10:43:53 -0000	1.699
+++ modules/system/system.module	25 May 2009 21:30:33 -0000
@@ -759,6 +759,38 @@
 }
 
 /**
+ * Implementation of hook_js_plugin().
+ */
+function system_js_plugin() {
+  // jQuery Form Plugin.
+  $plugins['form'] = array(
+    'title' => 'jQuery Form Plugin',
+    'project_page' => 'http://malsup.com/jquery/form/',
+    'version' => 2.16,
+    'download' => 'http://github.com/malsup/form/tarball/e52ed46bff664431a51d9562803acbcfbbd2e3b4',
+    'js' => array(
+      'misc/jquery.form.js',
+    ),
+  );
+
+  // Farbtastic.
+  $plugins['farbtastic'] = array(
+    'title' => 'Farbtastic',
+    'project_page' => 'http://acko.net/dev/farbtastic',
+    'version' => 1.2,
+    'download' => 'http://acko.net/files/farbtastic_/farbtastic12.zip',
+    'js' => array(
+      'misc/farbtastic/farbtastic.js',
+    ),
+    'css' => array(
+      'misc/farbtastic/farbtastic.css',
+    ),
+  );
+
+  return $plugins;
+}
+
+/**
  * Retrieve a blocked IP address from the database.
  *
  * @param $iid integer
Index: modules/simpletest/tests/common.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v
retrieving revision 1.43
diff -u -r1.43 common.test
--- modules/simpletest/tests/common.test	25 May 2009 05:20:16 -0000	1.43
+++ modules/simpletest/tests/common.test	25 May 2009 21:30:10 -0000
@@ -530,6 +530,25 @@
   }
 
   /**
+   * Checks to make sure that JavaScript plugins registry is built correctly.
+   */
+  function testPluginRegistry() {
+    $plugins = drupal_get_plugin();
+    $this->assertTrue(isset($plugins['farbtastic']) && isset($plugins['form']), t('The JavaScript plugin registry is built correctly.'));
+  }
+
+  /**
+   * Adds a JavaScript plugin to the page and tests for both it and its CSS.
+   */
+  function testRenderPlugin() {
+    drupal_add_plugin('farbtastic');
+    $javascript = drupal_get_js();
+    $stylesheets = drupal_get_css();
+    $this->assertTrue(strpos($javascript, 'misc/farbtastic/farbtastic.js') > 0, t('JavaScript plugins are rendered to the page.'));
+    $this->assertTrue(strpos($stylesheets, 'misc/farbtastic/farbtastic.css') > 0, t('JavaScript plugins render their CSS to the page.'));
+  }
+
+  /**
    * Test adding a JavaScript file with a different weight.
    */
   function testDifferentWeight() {
Index: modules/color/color.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/color/color.module,v
retrieving revision 1.58
diff -u -r1.58 color.module
--- modules/color/color.module	16 May 2009 16:08:57 -0000	1.58
+++ modules/color/color.module	25 May 2009 21:30:07 -0000
@@ -28,6 +28,35 @@
 }
 
 /**
+ * Implementation of hook_js_plugin_alter().
+ */
+function color_js_plugin_alter(&$plugins) {
+  if (isset($plugins['farbtastic'])) {
+    // Add the JavaScript and CSS for the color module.
+    $base = drupal_get_path('module', 'color');
+    $plugins['farbtastic']['js'][] = $base . '/color.js'; 
+    $plugins['farbtastic']['css'][] = $base . '/color.css';
+  }
+}
+
+/**
+ * Implementation of hook_js_plugin_add().
+ */
+function color_js_plugin_add($plugin, $theme = NULL) {
+  if ($plugin == 'farbtastic') {
+    // Add the settings for the current theme.
+    if (isset($theme)) {
+      $settings = array(
+        'color' => array(
+          'reference' => color_get_palette($theme, TRUE),
+        ),
+      );
+      drupal_add_js($settings, 'setting');
+    }
+  }
+}
+
+/**
  * Implementation of hook_form_FORM_ID_alter().
  */
 function color_form_system_theme_settings_alter(&$form, &$form_state) {
@@ -165,19 +194,10 @@
  * Form callback. Returns the configuration form.
  */
 function color_scheme_form(&$form_state, $theme) {
-  $base = drupal_get_path('module', 'color');
   $info = color_get_info($theme);
 
   // Add Farbtastic color picker.
-  drupal_add_css('misc/farbtastic/farbtastic.css', array('preprocess' => FALSE));
-  drupal_add_js('misc/farbtastic/farbtastic.js', array('weight' => JS_LIBRARY));
-
-  // Add custom CSS and JS.
-  drupal_add_css($base . '/color.css', array('preprocess' => FALSE));
-  drupal_add_js($base . '/color.js');
-  drupal_add_js(array('color' => array(
-    'reference' => color_get_palette($theme, TRUE)
-  )), 'setting');
+  drupal_add_plugin('farbtastic', $theme);
 
   // See if we're using a predefined scheme.
   $current = implode(',', variable_get('color_' . $theme . '_palette', array()));
