Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.925
diff -u -r1.925 common.inc
--- includes/common.inc	18 Jun 2009 21:19:01 -0000	1.925
+++ includes/common.inc	23 Jun 2009 16:17:52 -0000
@@ -2652,7 +2652,7 @@
 }
 
 /**
- * Add a JavaScript file, setting or inline code to the page.
+ * Add a JavaScript file, library, setting or inline code to the page.
  *
  * The behavior of this function depends on the parameters it is called with.
  * Generally, it handles the addition of JavaScript to the page, either as
@@ -2675,6 +2675,12 @@
  *   local server. Note that these external JavaScript references do not get
  *   aggregated when preprocessing is on.
  *
+ * - Add a JavaScript library ('library'):
+ *   Uses drupal_add_js_library() to add a JavaScript library to the page. Examples
+ *   of available JavaScript libraries are "jquery" or "farbtastic". Any
+ *   additional arguments that are passed to the drupal_add_js_library() can be
+ *   put into the $options['args'] array.
+ *
  * - Add settings ('setting'):
  *   Adds a setting to Drupal's global storage of JavaScript settings. Per-page
  *   settings are required by some modules to function properly. All settings
@@ -2699,6 +2705,9 @@
  *   - 'file': Path to the file relative to base_path().
  *   - 'inline': The JavaScript code that should be placed in the given scope.
  *   - 'external': The absolute path to a JavaScript file hosted externally.
+ *   - 'library': The JavaScript library name to add to the page. See
+ *       hook_js_library() for more information. Some examples are "jquery" or
+ *       "farbtastic".
  *   - 'setting': An array with configuration options as associative array. The
  *       array is directly placed in Drupal.settings. All modules should wrap
  *       their actual configuration settings in another variable to prevent
@@ -2710,8 +2719,8 @@
  *   always pass the string 'setting' only.
  *   - type
  *       The type of JavaScript that is to be added to the page. Allowed
- *       values are 'file', 'inline', 'external' or 'setting'. Defaults
- *       to 'file'.
+ *       values are 'file', 'inline', 'external', 'setting' or 'library'.
+ *       Defaults to 'file'.
  *   - scope
  *       The location in which you want to place the script. Possible values
  *       are 'header' or 'footer'. If your theme implements different regions,
@@ -2745,6 +2754,11 @@
  *       Aggregate the JavaScript if the JavaScript optimization setting has
  *       been toggled in admin/settings/performance. Note that JavaScript of
  *       type 'external' is not aggregated. Defaults to TRUE.
+ *   - args
+ *       When adding JavaScript of type "library", we have the ability to append
+ *       additional arguments to the call that's made to drupal_add_js_library().
+ *       Put these additional arguments you want appended to the call to
+ *       drupal_add_js_library() in this $options["args"] array.
  * @return
  *   The contructed array of JavaScript files.
  * @see drupal_get_js()
@@ -2779,15 +2793,6 @@
           'scope' => 'header',
           'weight' => JS_LIBRARY,
         ),
-        'misc/jquery.js' => array(
-          'data' => 'misc/jquery.js',
-          'type' => 'file',
-          'scope' => 'header',
-          'weight' => JS_LIBRARY - 2,
-          'cache' => TRUE,
-          'defer' => FALSE,
-          'preprocess' => TRUE,
-        ),
         'misc/drupal.js' => array(
           'data' => 'misc/drupal.js',
           'type' => 'file',
@@ -2798,6 +2803,8 @@
           'preprocess' => TRUE,
         ),
       );
+      // jQuery itself is registered as a library.
+      drupal_add_js_library('jquery');
     }
 
     switch ($options['type']) {
@@ -2811,6 +2818,15 @@
         $javascript[] = $options;
         break;
 
+      case 'library':
+        // Construct the additional arguments that are passed to
+        // drupal_add_js_library(). The first argument is the library name, while
+        // any additional arguments are found in the $options['args'] array.
+        $arguments = isset($options['args']) ? $options['args'] : array();
+        array_unshift($arguments, $data);
+        call_user_func_array('drupal_add_js_library', $arguments);
+        break;
+
       default: // 'file' and 'external'
         // Local and external files must keep their name as the associative key
         // so the same JavaScript file is not be added twice.
@@ -2954,6 +2970,120 @@
 }
 
 /**
+ * Adds multiple JavaScript files and CSS files for a JavaScript library.
+ *
+ * A JavaScript library could consist of a jQuery plugin, a separate JavaScript
+ * framework (such as Prototype or MooTools), or a library such as a WYSIWYG
+ * editor. Any time that there are multiple versions of a JavaScript package
+ * then it should be added via hook_js_libraries() and drupal_add_js_library().
+ * This allows other modules to check the version of the library, as well as
+ * modify it if different versions are required.
+ *
+ * @param $library
+ *   The name of the JavaScript library to add.
+ * @param ...
+ *   Additional arguments to pass to the add callbacks associated with the
+ *   library.
+ * @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_libraries()
+ * @see hook_js_libraries_alter()
+ */
+function drupal_add_js_library($library) {
+  $lib = drupal_get_js_library($library);
+  if ($lib == FALSE) {
+    watchdog('system', 'Warning: Missing JavaScript library %library.', array('%library' => $library), WATCHDOG_WARNING);
+    return FALSE;
+  }
+  // Add all the JavaScript, stylesheets and dependent libraries.
+  foreach (array('dependencies', 'js', 'css') as $type) {
+    if (isset($lib[$type]) && is_array($lib[$type])) {
+      foreach ($lib[$type] as $data => $options) {
+        // If the value is not an array, it's a filename or library name and
+        // passed as the first (and only) argument.
+        if (!is_array($options)) {
+          $data = $options;
+          $options = array();
+        }
+
+        // JavaScript and CSS is called with only two parameters ($data and
+        // $options), while the libraries/dependencies are designed to have any
+        // number of arguments. Due to this, we use call_user_func_array() as
+        // opposed to call_user_func().
+        if ($type == 'dependencies') {
+          // Prepend the library name as the first argument to be passed.
+          array_unshift($options, $data);
+          call_user_func_array('drupal_add_js_library', $options);
+        }
+        else {
+          // 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']);
+          }
+          // All JavaScript contained within the libraries are given a default
+          // weight of JS_LIBRARY.
+          if (!isset($options['weight'])) {
+            $options['weight'] = JS_LIBRARY;
+          }
+          call_user_func('drupal_add_' . $type, $data, $options);
+        }
+      }
+    }
+  }
+
+  // Call all of the add handlers associated with the library.
+  $output = array();
+  if (isset($lib['add callbacks']) && is_array($lib['add callbacks'])) {
+    foreach ($lib['add callbacks'] as $function) {
+      if (drupal_function_exists($function))  {
+        // Invoke the add handler, passing along all parameters.
+        $args = func_get_args();
+        $output[$function] = call_user_func_array($function, $args);
+      }
+    }
+  }
+  return $output;
+}
+
+/**
+ * Retrieves information from the JavaScript library registry.
+ *
+ * @param $library
+ *   (optional) If given, will provide the JavaScript library information for
+ *   just the given library name.
+ * @return
+ *   Returns all available JavaScript libraries, or the requested one from
+ *   the $library parameter.
+ * @see drupal_add_js_library()
+ * @see hook_js_libraries()
+ */
+function drupal_get_js_library($library = NULL) {
+  $libraries = &drupal_static(__FUNCTION__);
+
+  if (!isset($libraries)) {
+    // Invoke hook_js_libraries() to create the library registry.
+    $libraries = module_invoke_all('js_libraries');
+
+    // Invoke hook_js_libraries_alter() to allow modification of the
+    // registry. This is helpful to allow modules to update any libraries
+    // to later versions.
+    drupal_alter('js_libraries', $libraries);
+  }
+
+  if (isset($library)) {
+    return isset($libraries[$library]) ? $libraries[$library] : FALSE;
+  }
+  else {
+    return $libraries;
+  }
+}
+
+/**
  * Assist in adding the tableDrag JavaScript behavior to a themed table.
  *
  * Draggable tables should be used wherever an outline or list of sortable items
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.343
diff -u -r1.343 form.inc
--- includes/form.inc	20 Jun 2009 15:17:38 -0000	1.343
+++ includes/form.inc	23 Jun 2009 16:17:52 -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_js_library('jquery_form');
     drupal_add_js('misc/ahah.js');
 
     $ahah_binding = array(
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.43
diff -u -r1.43 system.api.php
--- modules/system/system.api.php	22 Jun 2009 09:10:06 -0000	1.43
+++ modules/system/system.api.php	23 Jun 2009 16:17:52 -0000
@@ -183,6 +183,121 @@
 }
 
 /**
+ * Registers any JavaScript libraries associated with the module.
+ *
+ * Modules implementing this return an array of arrays. The key to each sub-array
+ * is the machine readable name of the library. Each library may contain the
+ * following items:
+ *
+ * - title: The human readable name of the library.
+ * - website: The URL of the library's website.
+ * - version: (required) A string value determining the version of the included
+ *   library. This is intentionally not a float because a version like "1.2.3"
+ *   is not a valid float. Use version_compare() to compare versions.
+ * - js: An array of JavaScript elements to be processed when the library is
+ *   being added to the page. The array keys represents the $data parameter of
+ *   the drupal_add_js() call while the value becomes the $options argument. If
+ *   the array is just a bunch of values with integers for keys, then the value
+ *   becomes the $data parameter. See the library-1 example within hook_js_libraries()
+ *   for an example of this.
+ * - css: Similar to the "js" flag, provides an array of CSS elements to to be
+ *   processed when the library is being added the the page.
+ * - dependencies: An array of dependent libraries that will also be added when
+ *   processing the given library. The array key is the name of the library,
+ *   while the value is an array of additional arguments that will be passed
+ *   during the call to drupal_add_js_library().
+ * - add callbacks: An array of function callbacks that will be called when this
+ *   library is processed on the page. Any additional arguments that are appended
+ *   to the drupal_add_js_library() call become the arguments that are passed to
+ *   the add callback functions. See the library-2 example within
+ *   hook_js_libraries() for an example of "add callbacks".
+ *
+ * @return
+ *   An array defining any JavaScript libraries associated with the module.
+ * @see system_js_libraries()
+ */
+function hook_js_libraries() {
+  // Library One.
+  $libraries['library-1'] = array(
+    'title' => 'Library One',
+    'website' => 'http://example.com/library-1',
+    // Note that this is a string. Use version_compare() to compare.
+    'version' => '1.2',
+    'js' => array(
+      // Items that are just strings are interpreted as stright files. 
+      drupal_get_path('module', 'my_module') . '/library-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') . '/library-2.css' => array(
+        'type' => 'file',
+        'media' => 'screen',
+      ),
+    ),
+  );
+  // Library Two.
+  $libraries['library-2'] = array(
+    'title' => 'Library Two',
+    'website' => 'http://example.com/library-2',
+    'version' => '1.2',
+    'js' => array(
+      // JavaScript settings can be added by sending in an array.
+      array(
+        'type' => 'setting',
+        'data' => array('library-2' => TRUE),
+      ),
+    ),
+    // Any depending libraries can be added by using the "dependencies" key.
+    'dependencies' => array(
+      // Sending in a string will just add the depending library to the page.
+      'library-1',
+      // Sending in the library name along with an array of parameters will be
+      // sent in as additional arguments to drupal_add_js_library().
+      'library-3' => array(
+        'additional arguments',
+      ),
+    ),
+    // During inclusion of this library, all functions defined in the
+    // "add callbacks" array will be called.
+    'add callbacks' => array(
+      // The function named "library_2_added" will be called when library-2 is
+      // added to the page. The arguments passed to this function are this
+      // library's name, as well as any additional arguments that were passed
+      // during the call to drupal_add_js_library().
+      'library_2_added',
+    ),
+  );
+  return $libraries;
+}
+
+/**
+ * Alters the JavaScript library registry.
+ *
+ * @param $libraries
+ *   The JavaScript library registry.
+ * @return
+ *   An array with the updated JavaScript library registry.
+ * @see hook_js_libraries()
+ */
+function hook_js_libraries_alter(&$libraries) {
+  // Update Library One to version 1.6.
+  if (isset($libraries['library-1'])) {
+    // Check that Library One is older then version 1.6.
+    if (version_compare($libraries['library-1']['version'], '1.6', '<')) {
+      // The version number and CSS/JS files will change.
+      $libraries['library-1']['version'] = '1.6';
+      $libraries['library-1']['js'] = array(
+        drupal_get_path('module', 'library_1_update') . '/library-1.js',
+      );
+      $libraries['library-1']['css'] = array(
+        drupal_get_path('module', 'library_1_update') . '/library-1.css',
+      );
+    }
+  }
+}
+
+/**
  * 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.715
diff -u -r1.715 system.module
--- modules/system/system.module	23 Jun 2009 12:11:19 -0000	1.715
+++ modules/system/system.module	23 Jun 2009 16:17:52 -0000
@@ -789,6 +789,46 @@
 }
 
 /**
+ * Implementation of hook_js_libraries().
+ */
+function system_js_libraries() {
+  // jQuery.
+  $libraries['jquery'] = array(
+    'title' => 'jQuery',
+    'website' => 'http://jquery.com',
+    'version' => '1.3.2',
+    'js' => array(
+      'misc/jquery.js' => array('weight' => JS_LIBRARY - 2),
+    ),
+  );
+
+  // jQuery Form Plugin.
+  $libraries['jquery_form'] = array(
+    'title' => 'jQuery Form Plugin',
+    'website' => 'http://malsup.com/jquery/form/',
+    'version' => '2.16',
+    'js' => array(
+      'misc/jquery.form.js',
+    ),
+  );
+
+  // Farbtastic.
+  $libraries['farbtastic'] = array(
+    'title' => 'Farbtastic',
+    'website' => 'http://code.google.com/p/farbtastic/',
+    'version' => '1.2',
+    'js' => array(
+      'misc/farbtastic/farbtastic.js',
+    ),
+    'css' => array(
+      'misc/farbtastic/farbtastic.css',
+    ),
+  );
+
+  return $libraries;
+}
+
+/**
  * 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	23 Jun 2009 16:17:52 -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 library registry before each test.
     drupal_static_reset('drupal_add_js');
+    drupal_static_reset('drupal_get_js_library');
   }
 
   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,56 @@
   }
 
   /**
+   * Checks to make sure that JavaScript library registry is built correctly.
+   */
+  function testLibraryRegistry() {
+    $libraries = drupal_get_js_library();
+    $this->assertTrue(isset($libraries['farbtastic']) && isset($libraries['jquery_form']), t('The JavaScript library registry is built correctly.'));
+  }
+
+  /**
+   * Adds a JavaScript library to the page and tests for both it and its CSS.
+   */
+  function testLibraryRender() {
+    drupal_add_js_library('farbtastic');
+    $javascript = drupal_get_js();
+    $stylesheets = drupal_get_css();
+    $this->assertTrue(strpos($javascript, 'misc/farbtastic/farbtastic.js') > 0, t('JavaScript libraries are rendered to the page.'));
+    $this->assertTrue(strpos($stylesheets, 'misc/farbtastic/farbtastic.css') > 0, t('JavaScript libraries render their CSS to the page.'));
+  }
+
+  /**
+   * Adds a JavaScript library to the page and alters it.
+   *
+   * @see common_test_js_libraries_alter()
+   */
+  function testLibraryAlter() {
+    // Empty out messages.
+    drupal_get_messages();
+
+    // Add Farbtastic, which will fire common_test_js_alter().
+    drupal_add_js_library('farbtastic');
+    $library = drupal_get_js_library('farbtastic');
+    $this->assertTrue(array_search('common_test_js_library_add', $library['add callbacks']) !== FALSE, t('JavaScript libraries are alterable.'));
+    $this->assertTrue(strpos(theme('status_messages'), t('Add callback for the @library library fired.', array('@library' => 'farbtastic'))) !== FALSE, t('JavaScript library "add callback" functions are fired.'));
+
+    // The alter also adds a dependency on jQuery Form, so also check that.
+    $javascript = drupal_get_js();
+    $this->assertTrue(strpos($javascript, 'misc/jquery.form.js') > 0, t('Altered JavaScript library dependencies are added to the page.'));
+  }
+
+  /**
+   * Adds a JavaScript library through a call to drupal_add_js().
+   *
+   * @see drupal_add_js()
+   */
+  function testAddLibraryThroughJS() {
+    drupal_add_js('farbtastic', 'library');
+    $javascript = drupal_get_js();
+    $this->assertTrue(strpos($javascript, 'misc/farbtastic/farbtastic.js') > 0, t('JavaScript libraries can be added through a call to drupal_add_js().'));
+  }
+
+  /**
    * 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.59
diff -u -r1.59 color.module
--- modules/color/color.module	27 May 2009 18:33:55 -0000	1.59
+++ modules/color/color.module	23 Jun 2009 16:17:52 -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_js_library('farbtastic');
 
   // Add custom CSS and JS.
   drupal_add_css($base . '/color.css', array('preprocess' => FALSE));
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
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,21 @@
+<?php
+// $Id$
+
+/**
+ * Implementation of hook_js_libraries_alter().
+ */
+function common_test_js_libraries_alter(&$libraries) {
+  if (isset($libraries['farbtastic'])) {
+    // Set an "add" callback when farbtastic is added to the page.
+    $libraries['farbtastic']['add callbacks'][] = 'common_test_js_library_add';
+    // Also make Farbtastic depend on jQuery Form to test the library dependencies. 
+    $libraries['farbtastic']['dependencies'][] = 'jquery_form';
+  }
+}
+
+/**
+ * Add callback for the Farbtastic plugin.
+ */
+function common_test_js_library_add($library) {
+  drupal_set_message(t('Add callback for the @library library fired.', array('@library' => $library)));
+}
