Index: modules/color/color.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/color/color.module,v
retrieving revision 1.59
diff -u -r1.59 color.module
--- modules/color/color.module	27 May 2009 18:33:55 -0000	1.59
+++ modules/color/color.module	18 Jun 2009 21:05:57 -0000
@@ -169,8 +169,7 @@
   $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));
+  drupal_add_plugin('farbtastic');
 
   // Add custom CSS and JS.
   drupal_add_css($base . '/color.css', array('preprocess' => FALSE));
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.42
diff -u -r1.42 system.api.php
--- modules/system/system.api.php	18 Jun 2009 10:20:22 -0000	1.42
+++ modules/system/system.api.php	18 Jun 2009 21:05:58 -0000
@@ -183,6 +183,116 @@
 }
 
 /**
+ * Registers any JavaScript plugins associated with the module.
+ *
+ * Modules implementing this return an array of arrays. The key to each sub-array
+ * is the name of the plugin. Each plugin array may contain the following items:
+ *
+ * - title: The human readable name of the plugin.
+ * - project_page: The URL of the plugin's website.
+ * - version: (required) A decimal value determining the version of the included
+ *   plugin.
+ * - download: A URL to download the given version of the plugin.
+ * - js: An array of JavaScript elements to be processed when the plugin is
+ *   being added to the page. The array keys represents the $data parameter of
+ *   the drupal_add_js() call, and the value becomes the $options argument. If
+ *   the key is an integer, then the value becomes the $data parameter.
+ * - css: Similar to the "js" flag, provides an array of CSS elements to to be
+ *   processed when the plugin is being added the the page.
+ * - plugin: An array of dependent plugins that will also be added when processing
+ *   the given plugin. The array key is the name of the plugin, while the value
+ *   is an array of additional arguments that will be passed during the call to
+ *   drupal_add_plugin().
+ * - add: An array of function handlers that will called when this plugin is
+ *   processed on the page.
+ *
+ * @return
+ *   An array defining any JavaScript plugins associated with the module.
+ * @see system_js_plugin()
+ */
+function hook_js_plugin() {
+  // Plugin One.
+  $plugins['plugin-1'] = array(
+    'title' => 'Plugin One',
+    'project_page' => 'http://example.com/plugin-1',
+    'version' => 1.2,
+    'download' => 'http://example.com/plugin-1/plugin-1.js',
+    'js' => array(
+      // Items that are just strings are interpreted as stright files. 
+      drupal_get_path('module', 'my_module') . '/plugin-1.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', 'my_module') . '/plugin-2.css' => array(
+        'type' => 'file',
+        'media' => 'screen',
+      ),
+    ),
+  );
+  // Plugin Two.
+  $plugins['plugin-2'] = array(
+    'title' => 'Plugin Two',
+    'project_page' => 'http://example.com/plugin-2',
+    'version' => 1.2,
+    'download' => 'http://example.com/plugin-2/plugin-2.js',
+    'js' => array(
+      // JavaScript settings can be added by sending in an array.
+      array(
+        'type' => 'setting',
+        'data' => array('plugin-2' => TRUE),
+      ),
+    ),
+    // Any depending plugins can be added by using the "plugin" key.
+    'plugin' => array(
+      // Sending in a string will just add the depending plugin to the page.
+      'plugin-1',
+      // Sending in the plugin name along with an array of parameters will be
+      // sent in as additional arguments to drupal_add_plugin().
+      'plugin-3' => array(
+        'additional arguments',
+      )
+    ),
+    // The plugin add handlers are all called when the plugin is added to the
+    // page.
+    'add' => array(
+      // The function named "plugin_2_added" will be called when plugin-2 is
+      // added to the page. The arguments passed to this function are the
+      // additional arguments that were passed during the call to
+      // drupal_add_plugin().
+      'plugin_2_added',
+    ),
+  );
+  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 Plugin One to version 1.6.
+  if (isset($plugins['plugin-1'])) {
+    if ($plugins['plugin-1']['version'] < 1.6) {
+      // The version number and CSS/JS files will change.
+      $plugins['plugin-1']['version'] = 1.6;
+      $plugins['plugin-1']['js'] = array(
+        drupal_get_path('module', 'plugin_1_update') . '/plugin-1.js',
+      );
+      $plugins['plugin-1']['css'] = array(
+        drupal_get_path('module', 'plugin_1_update') . '/plugin-1.css',
+      );
+    }
+  }
+  return $plugins;
+}
+
+/**
  * Perform alterations before a page is rendered.
  *
  * Use this hook when you want to add, remove, or alter elements at the page
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.712
diff -u -r1.712 system.module
--- modules/system/system.module	16 Jun 2009 08:41:35 -0000	1.712
+++ modules/system/system.module	18 Jun 2009 21:06:00 -0000
@@ -789,6 +789,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.46
diff -u -r1.46 common.test
--- modules/simpletest/tests/common.test	12 Jun 2009 08:39:39 -0000	1.46
+++ modules/simpletest/tests/common.test	18 Jun 2009 21:05:57 -0000
@@ -425,7 +425,7 @@
   /**
    * Store configured value for JavaScript preprocessing.
    */
-  var $preprocess_js = NULL;
+  protected $preprocess_js = NULL;
 
   public static function getInfo() {
     return array(
@@ -437,19 +437,22 @@
 
   function setUp() {
     // Enable Locale and SimpleTest in the test environment.
-    parent::setUp('locale', 'simpletest');
+    parent::setUp('locale', 'simpletest', 'common_test');
 
     // Disable preprocessing
     $this->preprocess_js = variable_get('preprocess_js', 0);
     variable_set('preprocess_js', 0);
 
-    // Reset drupal_add_js() before each test.
+    // Reset drupal_add_js() and the plugin registry before each test.
     drupal_static_reset('drupal_add_js');
+    drupal_static_reset('drupal_get_plugin');
   }
 
   function tearDown() {
     // Restore configured value for JavaScript preprocessing.
     variable_set('preprocess_js', $this->preprocess_js);
+    // Clear any messages.
+    drupal_get_messages();
     parent::tearDown();
   }
 
@@ -541,6 +544,41 @@
   }
 
   /**
+   * 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.'));
+  }
+
+  /**
+   * Adds a JavaScript plugin to the page and alters it.
+   *
+   * @see common_test_js_alter()
+   */
+  function testAlterPlugin() {
+    // Empty out messages.
+    drupal_get_messages();
+
+    // Add farbtastic, which will fire common_test_js_alter().
+    drupal_add_plugin('farbtastic');
+    $plugin = drupal_get_plugin('farbtastic');
+    $this->assertTrue(array_search('common_test_js_plugin_add', $plugin['add']) !== FALSE, t('JavaScript plugins are alterable.'));
+    $this->assertTrue(strpos(theme('status_messages'), t('Add callback for the @plugin plugin fired.', array('@plugin' => 'farbtastic'))) !== FALSE, t('JavaScript plugin "add" callbacks are fired.'));
+  }
+
+  /**
    * Test adding a JavaScript file with a different weight.
    */
   function testDifferentWeight() {
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.924
diff -u -r1.924 common.inc
--- includes/common.inc	18 Jun 2009 16:03:30 -0000	1.924
+++ includes/common.inc	18 Jun 2009 21:05:55 -0000
@@ -2822,6 +2822,104 @@
 }
 
 /**
+ * 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 add handlers associated with the plugin.
+ * @return
+ *   An array containing the results from calling all the add handlers. The key
+ *   of the array is the add handler name, while the value is what was returned
+ *   from the call.
+ * @see hook_js_plugin()
+ * @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('plugin', 'js', 'css') 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);
+      }
+    }
+  }
+
+  // Call all of the add handlers associated with the plugin.
+  $output = array();
+  if (isset($plugin['add']) && is_array($plugin['add'])) {
+    foreach ($plugin['add'] as $function) {
+      if (drupal_function_exists($function))  {
+        // Invoke the add handler, passing in the additional parameters.
+        $args = func_get_args();
+
+        // Remove the plugin name from the arguments before calling the handler.
+        $output[$function] = call_user_func_array($function, $args);
+      }
+    }
+  }
+  return $output;
+}
+
+/**
+ * Retrieves information from the JavaScript plugin registry.
+ *
+ * @param $plugin
+ *   (optional) If given, will provide the JavaScript plugin information for
+ *   just the given plugin name.
+ * @return
+ *   Returns all available JavaScript plugins, or the requested one from $plugin.
+ * @see drupal_add_plugin()
+ * @see hook_js_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.342
diff -u -r1.342 form.inc
--- includes/form.inc	18 Jun 2009 15:48:13 -0000	1.342
+++ includes/form.inc	18 Jun 2009 21:05:56 -0000
@@ -2002,7 +2002,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/simpletest/tests/common_test.module
===================================================================
RCS file: modules/simpletest/tests/common_test.module
diff -N modules/simpletest/tests/common_test.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/common_test.module	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,20 @@
+<?php
+// $Id$
+
+/**
+ * Implementation of hook_js_plugin_alter().
+ */
+function common_test_js_plugin_alter(&$plugins) {
+  // Set an "add" callback when farbtastic is added to the page..
+  if (isset($plugins['farbtastic'])) {
+    $plugins['farbtastic']['add'][] = 'common_test_js_plugin_add';
+  }
+  return $plugins;
+}
+
+/**
+ * Add callback for the Farbtastic plugin.
+ */
+function common_test_js_plugin_add($plugin) {
+  drupal_set_message(t('Add callback for the @plugin plugin fired.', array('@plugin' => $plugin)));
+}
Index: modules/simpletest/tests/common_test.info
===================================================================
RCS file: modules/simpletest/tests/common_test.info
diff -N modules/simpletest/tests/common_test.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/common_test.info	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,8 @@
+; $Id$
+name = "Common Test"
+description = "Support module for core Drupal functionality tests."
+core = 7.x
+package = Testing
+files[] = common_test.module
+version = VERSION
+hidden = TRUE
