Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.429
diff -u -r1.429 theme.inc
--- includes/theme.inc	1 Jul 2008 20:22:22 -0000	1.429
+++ includes/theme.inc	9 Jul 2008 18:54:32 -0000
@@ -158,7 +158,7 @@
 
   // Add scripts used by this theme.
   foreach ($final_scripts as $script) {
-    drupal_add_js($script, 'theme');
+    drupal_add_js($script, array('weight' => JS_THEME_WEIGHT));
   }
 
   $theme_engine = NULL;
@@ -1282,7 +1282,7 @@
 
   // Add sticky headers, if applicable.
   if (count($header)) {
-    drupal_add_js('misc/tableheader.js');
+    drupal_add_js('misc/tableheader.js', array('weight' => JS_CORE_WEIGHT));
     // Add 'sticky-enabled' class to the table to identify it for JS.
     // This is needed to target tables constructed by this function.
     $attributes['class'] = empty($attributes['class']) ? 'sticky-enabled' : ($attributes['class'] . ' sticky-enabled');
@@ -1399,7 +1399,7 @@
  * Returns a header cell for tables that have a select all functionality.
  */
 function theme_table_select_header_cell() {
-  drupal_add_js('misc/tableselect.js');
+  drupal_add_js('misc/tableselect.js', array('weight' => JS_CORE_WEIGHT));
 
   return array('class' => 'select-all');
 }
Index: includes/batch.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/batch.inc,v
retrieving revision 1.20
diff -u -r1.20 batch.inc
--- includes/batch.inc	24 Jun 2008 21:51:02 -0000	1.20
+++ includes/batch.inc	9 Jul 2008 18:54:14 -0000
@@ -80,7 +80,7 @@
   // and the initialization and error messages.
   $current_set = _batch_current_set();
   drupal_set_title($current_set['title']);
-  drupal_add_js('misc/progress.js', 'core', 'header', FALSE, FALSE);
+  drupal_add_js('misc/progress.js', array('cache' => FALSE, 'weight' => JS_CORE_WEIGHT, 'attributes' => array('defer' => 'defer')));
 
   $url = url($batch['url'], array('query' => array('id' => $batch['id'])));
   $js_setting = array(
@@ -91,7 +91,7 @@
     ),
   );
   drupal_add_js($js_setting, 'setting');
-  drupal_add_js('misc/batch.js', 'core', 'header', FALSE, FALSE);
+  drupal_add_js('misc/batch.js', array('cache' => FALSE, 'weight' => JS_CORE_WEIGHT, 'attributes' => array('defer' => 'defer')));
 
   $output = '<div id="progress"></div>';
   return $output;
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.275
diff -u -r1.275 form.inc
--- includes/form.inc	25 Jun 2008 09:57:07 -0000	1.275
+++ includes/form.inc	9 Jul 2008 18:54:28 -0000
@@ -1489,7 +1489,7 @@
  */
 function theme_fieldset($element) {
   if ($element['#collapsible']) {
-    drupal_add_js('misc/collapse.js');
+    drupal_add_js('misc/collapse.js', array('weight' => JS_CORE_WEIGHT));
 
     if (!isset($element['#attributes']['class'])) {
       $element['#attributes']['class'] = '';
@@ -1787,8 +1787,8 @@
   // 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']['path']) && isset($element['#ahah']['event']) && !isset($js_added[$element['#id']])) {
-    drupal_add_js('misc/jquery.form.js');
-    drupal_add_js('misc/ahah.js');
+    drupal_add_js('misc/jquery.form.js', array('weight' => JS_CORE_WEIGHT));
+    drupal_add_js('misc/ahah.js', array('weight' => JS_CORE_WEIGHT));
 
     $ahah_binding = array(
       'url'      => url($element['#ahah']['path']),
@@ -1813,7 +1813,7 @@
 
     // Add progress.js if we're doing a bar display.
     if ($ahah_binding['progress']['type'] == 'bar') {
-      drupal_add_js('misc/progress.js');
+      drupal_add_js('misc/progress.js', array('weight' => JS_CORE_WEIGHT));
     }
 
     drupal_add_js(array('ahah' => array($element['#id'] => $ahah_binding)), 'setting');
@@ -2001,7 +2001,7 @@
   $output = '';
 
   if ($element['#autocomplete_path']) {
-    drupal_add_js('misc/autocomplete.js');
+    drupal_add_js('misc/autocomplete.js', array('weight' => JS_CORE_WEIGHT));
     $class[] = 'form-autocomplete';
     $extra =  '<input class="autocomplete" type="hidden" id="' . $element['#id'] . '-autocomplete" value="' . check_url(url($element['#autocomplete_path'], array('absolute' => TRUE))) . '" disabled="disabled" />';
   }
@@ -2053,16 +2053,14 @@
 
   // Add teaser behavior (must come before resizable)
   if (!empty($element['#teaser'])) {
-    drupal_add_js('misc/teaser.js');
-    // Note: arrays are merged in drupal_get_js().
-    drupal_add_js(array('teaserCheckbox' => array($element['#id'] => $element['#teaser_checkbox'])), 'setting');
-    drupal_add_js(array('teaser' => array($element['#id'] => $element['#teaser'])), 'setting');
+    drupal_add_js('misc/teaser.js', array('weight' => JS_CORE_WEIGHT));
+    drupal_add_js(array('teaserCheckbox' => array($element['#id'] => $element['#teaser_checkbox']), 'teaser' => array($element['#id'] => $element['#teaser'])), 'setting');
     $class[] = 'teaser';
   }
 
   // Add resizable behavior
   if ($element['#resizable'] !== FALSE) {
-    drupal_add_js('misc/textarea.js');
+    drupal_add_js('misc/textarea.js', array('weight' => JS_CORE_WEIGHT));
     $class[] = 'resizable';
   }
 
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.776
diff -u -r1.776 common.inc
--- includes/common.inc	2 Jul 2008 19:36:52 -0000	1.776
+++ includes/common.inc	9 Jul 2008 18:54:22 -0000
@@ -25,6 +25,26 @@
 define('SAVED_DELETED', 3);
 
 /**
+ * Weight of JavaScript for files loaded from core (files in /misc/).
+ */
+define('JS_CORE_WEIGHT', -20);
+
+/**
+ * Weight of JavaScript files loaded by modules.
+ */
+define('JS_MODULE_WEIGHT', 0);
+
+/**
+ * Weight of JavaScript files loaded by themes.
+ */
+define('JS_THEME_WEIGHT', 20);
+
+/**
+ * Weight of JavaScript libraries needed by other scripts, eg. jQuery.
+ */
+define('JS_LIBRARY_WEIGHT', -100);
+
+/**
  * Set content for a specified region.
  *
  * @param $region
@@ -1445,9 +1465,9 @@
 function l($text, $path, $options = array()) {
   // Merge in defaults.
   $options += array(
-      'attributes' => array(),
-      'html' => FALSE,
-    );
+    'attributes' => array(),
+    'html' => FALSE,
+  );
 
   // Append active class.
   if ($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) {
@@ -1912,16 +1932,17 @@
  * reference to an existing file or as inline code. The following actions can be
  * performed using this function:
  *
- * - Add a file ('core', 'module' and 'theme'):
- *   Adds a reference to a JavaScript file to the page. JavaScript files
- *   are placed in a certain order, from 'core' first, to 'module' and finally
- *   'theme' so that files, that are added later, can override previously added
- *   files with ease.
+ * - Add a file ('file'):
+ *   Adds a reference to a JavaScript file to the page and supports files on
+ *   external servers. JavaScript files are placed in a certain order depending
+ *   on their given weight. Files from core, modules, and themes have predefined
+ *   constants for their weights of JS_CORE_WEIGHT (-20), JS_MODULE_WEIGHT (0),
+ *   and JS_THEME_WEIGHT (20).
  *
  * - Add inline JavaScript code ('inline'):
  *   Executes a piece of JavaScript code on the current page by placing the code
- *   directly in the page. This can, for example, be useful to tell the user that
- *   a new message arrived, by opening a pop up, alert box etc.
+ *   directly in the page. This can, for example, be useful to tell the user
+ *   that a new message arrived, by opening a pop up, alert box, etc.
  *
  * - Add settings ('setting'):
  *   Adds a setting to Drupal's global storage of JavaScript settings. Per-page
@@ -1929,92 +1950,192 @@
  *   will be accessible at Drupal.settings.
  *
  * @param $data
- *   (optional) If given, the value depends on the $type parameter:
- *   - 'core', 'module' or 'theme': Path to the file relative to base_path().
+ *   (optional) If given, the value depends on the $options parameter:
+ *   - 'file': Either the path to the file relative to base_path(), or the
+ *       absolute URL to the Javascript file on an external server.
  *   - 'inline': The JavaScript code that should be placed in the given scope.
  *   - 'setting': An array with configuration options as associative array. The
- *       array is directly placed in Drupal.settings. You might want to wrap your
- *       actual configuration settings in another variable to prevent the pollution
- *       of the Drupal.settings namespace.
- * @param $type
- *   (optional) The type of JavaScript that should be added to the page. Allowed
- *   values are 'core', 'module', 'theme', 'inline' and 'setting'. You
- *   can, however, specify any value. It is treated as a reference to a JavaScript
- *   file. Defaults to 'module'.
- * @param $scope
- *   (optional) The location in which you want to place the script. Possible
- *   values are 'header' and 'footer' by default. If your theme implements
- *   different locations, however, you can also use these.
- * @param $defer
- *   (optional) If set to TRUE, the defer attribute is set on the <script> tag.
- *   Defaults to FALSE. This parameter is not used with $type == 'setting'.
- * @param $cache
- *   (optional) If set to FALSE, the JavaScript file is loaded anew on every page
- *   call, that means, it is not cached. Defaults to TRUE. Used only when $type
- *   references a JavaScript file.
- * @param $preprocess
- *   (optional) Should this JS file be aggregated if this
- *   feature has been turned on under the performance section?
- * @return
- *   If the first parameter is NULL, the JavaScript array that has been built so
- *   far for $scope is returned. If the first three parameters are NULL,
- *   an array with all scopes is returned.
+ *       array is directly placed in Drupal.settings. You might want to wrap 
+ *       your actual configuration settings in another variable to prevent the 
+ *       pollution of the Drupal.settings namespace.
+ * @param $options
+ *   (optional) A string for the type ('file', 'inline', 'setting'), or an array
+ *   which can have any or all of the following keys (these are not valid with 
+ *   type => 'setting'):
+ *   - type
+ *       The type of JavaScript that should be added to the page. Allowed
+ *       values are 'file', 'inline' and 'setting'.
+ *   - scope
+ *       The location in which you want to place the script. Possible
+ *       values are 'header' and 'footer' by default. If your theme implements
+ *       different locations, however, you can also use these.
+ *   - weight
+ *       The weight determines the order of files in output. Lower weights mean
+ *       the scripts are loaded sooner. Default to JS_MODULE_WEIGHT if none 
+ *       specified. Constants are available as JS_CORE_WEIGHT (-20), 
+ *       JS_MODULE_WEIGHT (0), and JS_THEME_WEIGHT (20).
+ *   -  cache
+ *       If set to FALSE, the JavaScript file is loaded anew on every page
+ *       call, that means, it is not cached. Defaults to TRUE. Used only when 
+ *       $type references a JavaScript file.
+ *   - aggregate
+ *       Should this JS file be aggregated if this feature has been turned
+ *       on under the performance section.
+ *   - attributes
+ *       An array of attributes to be added to the script tag. Will be run 
+ *       through drupal_attributes().
+ * @param $reset
+ *   Resets the currently loaded JavaScript.
+ * @return
+ *   An array of JavaScript files to include - the array has three keys:
+ *    - 'file'
+ *        An array of files keyed by path (the value is the $options that was 
+ *        passed in).
+ *    - 'inline'
+ *        An array of code to be put inline.  The key is the code and the value
+ *        is the $options.
+ *    - 'setting'
+ *        This one is different; it is keyed by scope (region to put the
+ *        JavaScript in). Inside each scope is an aggregation of the variables 
+ *        that were added to the settings.
  */
-function drupal_add_js($data = NULL, $type = 'module', $scope = 'header', $defer = FALSE, $cache = TRUE, $preprocess = TRUE) {
+function drupal_add_js($data = NULL, $options = array(), $reset = FALSE) {
   static $javascript = array();
 
-  if (isset($data)) {
-
-    // Add jquery.js and drupal.js, as well as the basePath setting, the
-    // first time a Javascript file is added.
-    if (empty($javascript)) {
-      $javascript['header'] = array(
-        'core' => array(
-          'misc/jquery.js' => array('cache' => TRUE, 'defer' => FALSE, 'preprocess' => TRUE),
-          'misc/drupal.js' => array('cache' => TRUE, 'defer' => FALSE, 'preprocess' => TRUE),
+  // Add jquery.js and drupal.js, as well as the basePath setting, the
+  // first time a Javascript file is added.
+  if (empty($javascript) || $reset) {
+    $javascript = array(
+      'header' => array(
+        'file' => array(
+          'misc/jquery.js' => array(
+            'weight' => JS_LIBRARY_WEIGHT,
+            'cache' => TRUE,
+            'aggregate' => TRUE,
+            'attributes' => array('type' => 'text/javascript'),
+          ),
+          'misc/drupal.js' => array(
+            'weight' => -99,
+            'cache' => TRUE,
+            'aggregate' => TRUE,
+            'attributes' => array('type' => 'text/javascript'),
+          ),
         ),
-        'module' => array(),
-        'theme' => array(),
         'setting' => array(
-          array('basePath' => base_path()),
+          'basePath' => base_path(),
         ),
         'inline' => array(),
-      );
-    }
+      )
+    );
+  }
+  
+  // Reset to default if necessary.
+  if ($reset && isset($data)) {
+    $javascript = $data;
+    return $javascript;
+  }
+  
 
-    if (isset($scope) && !isset($javascript[$scope])) {
-      $javascript[$scope] = array('core' => array(), 'module' => array(), 'theme' => array(), 'setting' => array(), 'inline' => array());
+  if (isset($data)) {
+    if (isset($options) && !is_array($options)) {
+      $options = array('type' => $options);
     }
 
-    if (isset($type) && isset($scope) && !isset($javascript[$scope][$type])) {
-      $javascript[$scope][$type] = array();
-    }
+    $options += array(
+      'type' => 'file',
+      'weight' => JS_MODULE_WEIGHT,
+      'scope' => 'header',
+      'cache' => TRUE,
+      'aggregate' => ((isset($options['cache']) && $options['cache']) || !isset($options['cache']) ? TRUE : FALSE),
+      'remove' => FALSE,
+      'attributes' => array('type' => 'text/javascript'),
+    );
 
+    $type = $options['type'];
+    unset($options['type']);
+    $scope = $options['scope'];
+    unset($options['scope']);
+    
+    // Remove given JavaScript file if it exists, otherwise do nothing.
+    if ($options['remove']) {
+      if (isset($javascript[$scope][$type][$data])) {
+        unset($javascript[$scope][$type][$data]);
+      }
+      return $javascript;
+    }
+    
+    // Make sure that array template is set for this scope.
+    $javascript += array(
+      $scope => array(
+        'file' => array(),
+        'setting' => array(),
+        'inline' => array()
+      )
+    );
+    
     switch ($type) {
       case 'setting':
-        $javascript[$scope][$type][] = $data;
+        $javascript[$scope]['setting'] = array_merge_recursive($javascript[$scope]['setting'], $data);
         break;
       case 'inline':
-        $javascript[$scope][$type][] = array('code' => $data, 'defer' => $defer);
+        if (!isset($javascript[$scope]['file'][$data])) {
+          $javascript[$scope]['inline'][$data] = $options;
+          $sort = array();
+          foreach ($javascript[$scope]['inline'] as $code => $settings) {
+            $sort[$code] = $settings['weight'];
+          }
+          $inline_data = $javascript[$scope]['inline'];
+          array_multisort($sort, SORT_NUMERIC, $inline_data);
+          $javascript[$scope]['inline'] = $inline_data;
+        }
+        break;
+      case 'file':
+        if (!isset($javascript[$scope]['file'][$data])) {
+          $javascript[$scope]['file'][$data] = $options;
+          $sort = array();
+          foreach ($javascript[$scope]['file'] as $uri => $settings) {
+            if (isset($sort[$settings['weight']])) {
+              $sort[$settings['weight']][] = $uri;
+            }
+            else {
+              $sort[$settings['weight']] = array($uri);
+            }
+          }
+          ksort($sort);
+          $files = array();
+          foreach ($sort as $uri_array) {
+            foreach ($uri_array as $uri) {
+              $files[$uri] = $javascript[$scope]['file'][$uri];
+            }
+          }
+          $javascript[$scope]['file'] = $files;
+        }
         break;
-      default:
-        // If cache is FALSE, don't preprocess the JS file.
-        $javascript[$scope][$type][$data] = array('cache' => $cache, 'defer' => $defer, 'preprocess' => (!$cache ? FALSE : $preprocess));
     }
   }
 
-  if (isset($scope)) {
+  return $javascript;
+}
 
-    if (isset($javascript[$scope])) {
-      return $javascript[$scope];
-    }
-    else {
-      return array();
-    }
-  }
-  else {
-    return $javascript;
-  }
+/**
+ * Removes a specified JavaScript include from the include list. This is only
+ * allowed for JavaScript files, as it matches based on file URL.
+ *
+ * @param $data
+ *   Same as for drupal_add_js.
+ * @param $type
+ *   (optional) The type of JavaScript that should be added to the page. Allowed
+ *   values are 'file', 'inline' and 'setting'.
+ * @return
+ *   The resulting call to drupal_add_js.
+ * @see drupal_add_js
+ */
+function drupal_remove_js($data, $type = 'file') {
+  $options = array(
+    'type' => $type,
+    'remove' => TRUE
+  );
+  return drupal_add_js($data, $options);
 }
 
 /**
@@ -2033,28 +2154,34 @@
  *   JavaScript array for the given scope.
  * @return
  *   All JavaScript code segments and includes for the scope as HTML tags.
+ * @see drupal_add_js
  */
 function drupal_get_js($scope = 'header', $javascript = NULL) {
-  if ((!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') && function_exists('locale_update_js_files')) {
-    locale_update_js_files();
-  }
 
+	// Default to drupal_add_js().
   if (!isset($javascript)) {
-    $javascript = drupal_add_js(NULL, NULL, $scope);
+    $javascript = drupal_add_js();
   }
-
-  if (empty($javascript)) {
+  
+  // Determine JavaScript files relevant to the current scope.
+  if (isset($javascript[$scope])) {
+    $javascript = $javascript[$scope];
+  }
+  else {
     return '';
   }
-
-  $output = '';
-  $preprocessed = '';
-  $no_preprocess = array('core' => '', 'module' => '', 'theme' => '');
-  $files = array();
-  $preprocess_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
+  
+  // Check if JS preprocessing is enabled.
+  $aggregate_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
+  // Get the file directory path.
   $directory = file_directory_path();
+  // Check whether the file directory path is writable.
   $is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC);
-
+  
+  // Create the js/ within the files folder.
+  $jspath = file_create_path('js');
+  file_check_directory($jspath, FILE_CREATE_DIRECTORY);
+  
   // A dummy query-string is added to filenames, to gain control over
   // browser-caching. The string changes on every update or full cache
   // flush, forcing browsers to load a new copy of the files, as the
@@ -2062,45 +2189,154 @@
   // get time() as query-string instead, to enforce reload on every
   // page request.
   $query_string = '?' . substr(variable_get('css_js_query_string', '0'), 0, 1);
+  
+  // Call hook_js_alter() in all relevant modules.
+  drupal_alter('js', $javascript, $scope);
+  
+  // Now handle caching and aggregation.
+  if ($is_writable) {
+    
+    // Cache and/or aggregate all files if needed.
+    if (count($javascript['file']) > 0) {
+      
+      // Cache all external files while building a list of files to aggregate
+      $aggregate_files = array();
+      $new_js = array();
+      foreach ($javascript['file'] as $path => $options) {
+        // Cache any external JS files locally if specified.
+        $external = valid_url($path, TRUE);
+        if ($options['cache'] && $external && !file_exists($jspath . '/' . md5($path) . '.js')) {
+          if ($local_path = drupal_cache_external_js($path)) {
+            $path = $local_path;
+            $external = FALSE;
+          }
+          else {
+            // We failed to cache the file, but make sure it isn't aggregated.
+            watchdog('javascript', 'Failed to cache JavaScript file from %js.', array('%js' => $path), WATCHDOG_ERROR);
+          }
+        }
+        
+        // Be sure to add all files into the new array. This is neccessary for
+        // cached files so the order is preserved after the file is cached.
+        $new_js[$path] = $options;
+        
+        // After caching an external script locally, mark it for aggregation if
+        // necessary.
+        if ($aggregate_js && $options['aggregate'] && !$external) {
+          $aggregate_files[$path] = $options;
+        }
+      }
+      
+      // Update the $javascript to get our new version that may included cached
+      // files.
+      $javascript['file'] = $new_js;
+      if ($aggregate_js) {
+        $filename = md5(serialize($aggregate_files) . $query_string) . '.js';
+        $unprocessed = array();
+        $preprocess_file = drupal_aggregate_js($aggregate_files, $filename);
+        
+        // If we aggregated any files, be sure to add the file back into the
+        // array in the same position as the first aggregated file, making sure
+        // to remove all aggregated files.
+        if ($preprocess_file) {
+          $new_js = array();
+          $first_js = TRUE;
+          foreach ($javascript['file'] as $path => $options) {
+            if (isset($aggregate_files[$path])) {
+              if ($first_js) {
+                $new_js[$preprocess_file] = array(
+                  'weight' => JS_MODULE_WEIGHT,
+                  'scope' => 'header',
+                  'cache' => TRUE,
+                  'aggregate' => FALSE,
+                  'attributes' => array('type' => 'text/javascript'),
+                );
+                $first_js = FALSE;
+              }
+            }
+            else {
+              $new_js[$path] = $options;
+            }
+          }
+          
+          $javascript['file'] = $new_js;
+        }
+      }
+    }
+  }
 
+  $renderable_js = array();
   foreach ($javascript as $type => $data) {
 
     if (!$data) continue;
 
     switch ($type) {
       case 'setting':
-        $output .= '<script type="text/javascript">jQuery.extend(Drupal.settings, ' . drupal_to_js(call_user_func_array('array_merge_recursive', $data)) . ");</script>\n";
+        if (!empty($data)) {
+          $renderable_js[] = array(
+            'type' => 'setting',
+            'data' => $data,
+          );
+        }
         break;
       case 'inline':
-        foreach ($data as $info) {
-          $output .= '<script type="text/javascript"' . ($info['defer'] ? ' defer="defer"' : '') . '>' . $info['code'] . "</script>\n";
+        foreach ($data as $js => $options) {
+          $renderable_js[] = array(
+            'type' => 'inline',
+            'data' => $js,
+            'attributes' => $options['attributes'],
+          );
         }
         break;
+      case 'file':
       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) {
-          if (!$info['preprocess'] || !$is_writable || !$preprocess_js) {
-            $no_preprocess[$type] .= '<script type="text/javascript"' . ($info['defer'] ? ' defer="defer"' : '') . ' src="' . base_path() . $path . ($info['cache'] ? $query_string : '?' . time()) . "\"></script>\n";
+          // Create the source of the Javscript, checking if base_path()
+          // is required, as well as placing the dummy query string at the
+          // end of the path.
+          if (valid_url($path, TRUE)) {
+            $info['attributes']['src'] = $path;
           }
           else {
-            $files[$path] = $info;
+            $info['attributes']['src'] = base_path() . $path . ($info['cache'] ? $query_string : '?' . time());
           }
+          $renderable_js[] = array(
+            'type' => 'file',
+            'attributes' => $info['attributes']
+          );
         }
     }
   }
+  
+  return theme('scripts', $renderable_js, $scope);
+}
 
-  // Aggregate any remaining JS files that haven't already been output.
-  if ($is_writable && $preprocess_js && count($files) > 0) {
-    $filename = md5(serialize($files) . $query_string) . '.js';
-    $preprocess_file = drupal_build_js_cache($files, $filename);
-    $preprocessed .= '<script type="text/javascript" src="' . base_path() . $preprocess_file . '"></script>' . "\n";
-  }
-
-  // Keep the order of JS files consistent as some are preprocessed and others are not.
-  // Make sure any inline or JS setting variables appear last after libraries have loaded.
-  $output = $preprocessed . implode('', $no_preprocess) . $output;
+/**
+ * Themes a list of scripts, which are then rendered to the page.
+ *
+ * @param $scripts
+ *   An array of scripts to be output.
+ * @param $scope
+ *   The scope of the JavaScript: where it's going to be located
+ *   on the page.
+ * @return
+ *   HTML containing the script tags.
+ */
+function theme_scripts($scripts, $scope) {
+  $output = '';
 
+  foreach ($scripts as $script) {
+    switch ($script['type']) {
+      case 'setting':
+        $output .= '<script type="text/javascript">jQuery.extend(Drupal.settings, ' . drupal_to_js($script['data']) . ");</script>\n";
+        break;
+      case 'inline':
+        $output .= '<script' . drupal_attributes($script['attributes']) . '>' . $script['data'] . "</script>\n";
+        break;
+      case 'file':
+        $output .= '<script' . drupal_attributes($script['attributes']) . "></script>\n";
+    }
+  }
   return $output;
 }
 
@@ -2214,7 +2450,7 @@
 function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgroup = NULL, $source = NULL, $hidden = TRUE, $limit = 0) {
   static $js_added = FALSE;
   if (!$js_added) {
-    drupal_add_js('misc/tabledrag.js', 'core');
+    drupal_add_js('misc/tabledrag.js', array('weight' => JS_CORE_WEIGHT));
     $js_added = TRUE;
   }
 
@@ -2234,15 +2470,15 @@
 
 /**
  * Aggregate JS files, putting them in the files directory.
- *
+ * 
  * @param $files
  *   An array of JS files to aggregate and compress into one file.
  * @param $filename
  *   The name of the aggregate JS file.
  * @return
- *   The name of the JS file.
+ *   The name of the JS file or FALSE on failure.
  */
-function drupal_build_js_cache($files, $filename) {
+function drupal_aggregate_js($files, $filename) {
   $contents = '';
 
   // Create the js/ within the files folder.
@@ -2250,11 +2486,15 @@
   file_check_directory($jspath, FILE_CREATE_DIRECTORY);
 
   if (!file_exists($jspath . '/' . $filename)) {
-    // Build aggregate JS file.
     foreach ($files as $path => $info) {
-      if ($info['preprocess']) {
-        // Append a ';' after each JS file to prevent them from running together.
-        $contents .= file_get_contents($path) . ';';
+      $tmp = file_get_contents($path);
+      if ($tmp) {
+        // Append a ';' after each JS file to prevent them from running
+        // together.
+        $contents .= $tmp . ';';
+      }
+      else {
+        return FALSE;
       }
     }
 
@@ -2266,10 +2506,36 @@
 }
 
 /**
+ * Cache locally external JS files.
+ *
+ * @param $path 
+ *   A URL to an external JavaScript file.
+ * @return 
+ *   A string representing the local version or FALSE if caching failed.
+ */
+function drupal_cache_external_js($path) {
+
+  // Create the js/ within the files folder.
+  $jspath = file_create_path('js');
+  if (!file_check_directory($jspath, FILE_CREATE_DIRECTORY)) {
+    return FALSE;
+  }
+
+  $request = drupal_http_request($path);
+  if ($request->code == '200') {
+    $filename = md5($path) . '.js';
+    return file_save_data($request->data, $jspath . '/' . $filename, FILE_EXISTS_REPLACE);
+  }
+  
+  return FALSE;
+}
+
+/**
  * Delete all cached JS files.
  */
 function drupal_clear_js_cache() {
   file_scan_directory(file_create_path('js'), '.*', array('.', '..', 'CVS'), 'file_delete', TRUE);
+  // TODO: Move this locale.module-specific code into locale.module somehow.
   variable_set('javascript_parsed', array());
 }
 
@@ -2277,6 +2543,11 @@
  * Converts a PHP variable into its Javascript equivalent.
  *
  * We use HTML-safe strings, i.e. with <, > and & escaped.
+ * @param $var
+ *   The variable to encode.
+ * @return
+ *   A string, which is a JavaScript-encoded version of $var.
+ * @see drupal_json
  */
 function drupal_to_js($var) {
   // json_encode() does not escape <, > and &, so we do it with str_replace()
@@ -2291,6 +2562,7 @@
  *
  * @param $var
  *   (optional) If set, the variable will be converted to JSON and output.
+ * @see drupal_to_js
  */
 function drupal_json($var = NULL) {
   // We are returning JavaScript, so tell the browser.
@@ -3033,6 +3305,10 @@
     'form_element' => array(
       'arguments' => array('element' => NULL, 'value' => NULL),
     ),
+    // theme_scripts() exists in form.inc.
+    'scripts' => array(
+      'arguments' => array('scripts' => NULL, 'scope' => NULL),
+    ),
   );
 }
 
Index: modules/user/user.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.admin.inc,v
retrieving revision 1.22
diff -u -r1.22 user.admin.inc
--- modules/user/user.admin.inc	7 May 2008 19:34:24 -0000	1.22
+++ modules/user/user.admin.inc	9 Jul 2008 18:54:46 -0000
@@ -83,7 +83,7 @@
     );
   }
 
-  drupal_add_js('misc/form.js', 'core');
+  drupal_add_js('misc/form.js', array('weight' => JS_CORE_WEIGHT));
 
   return $form;
 }
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.911
diff -u -r1.911 user.module
--- modules/user/user.module	27 Jun 2008 07:25:11 -0000	1.911
+++ modules/user/user.module	9 Jul 2008 18:54:51 -0000
@@ -2122,7 +2122,7 @@
   global $user;
   // Only need to do once per page.
   if (!$complete) {
-    drupal_add_js(drupal_get_path('module', 'user') . '/user.js', 'module');
+    drupal_add_js(drupal_get_path('module', 'user') . '/user.js');
 
     drupal_add_js(array(
       'password' => array(
Index: modules/system/system.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v
retrieving revision 1.78
diff -u -r1.78 system.admin.inc
--- modules/system/system.admin.inc	1 Jul 2008 20:36:40 -0000	1.78
+++ modules/system/system.admin.inc	9 Jul 2008 18:54:44 -0000
@@ -1522,7 +1522,7 @@
  * @see system_date_time_settings_submit()
  */
 function system_date_time_settings() {
-  drupal_add_js(drupal_get_path('module', 'system') . '/system.js', 'module');
+  drupal_add_js(drupal_get_path('module', 'system') . '/system.js');
   drupal_add_js(array('dateTime' => array('lookup' => url('admin/settings/date-time/lookup'))), 'setting');
 
   // Date settings:
@@ -1730,7 +1730,7 @@
 
   if (!variable_get('clean_url', 0)) {
     if (strpos(request_uri(), '?q=') !== FALSE) {
-      drupal_add_js(drupal_get_path('module', 'system') . '/system.js', 'module');
+      drupal_add_js(drupal_get_path('module', 'system') . '/system.js');
 
       $form['clean_url']['#description'] .= ' <span>' . t('Before enabling clean URLs, you must perform a test to determine if your server is properly configured. If you are able to see this page again after clicking the "Run the clean URL test" link, the test has succeeded and the radio buttons above will be available. If instead you are directed to a "Page not found" error, you will need to change the configuration of your server. The <a href="@handbook">handbook page on Clean URLs</a> has additional troubleshooting information.', array('@handbook' => 'http://drupal.org/node/15365')) . '</span>';
 
Index: modules/simpletest/simpletest.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.module,v
retrieving revision 1.6
diff -u -r1.6 simpletest.module
--- modules/simpletest/simpletest.module	5 Jul 2008 17:48:33 -0000	1.6
+++ modules/simpletest/simpletest.module	9 Jul 2008 18:54:39 -0000
@@ -194,7 +194,7 @@
  */
 function theme_simpletest_test_form($form) {
   drupal_add_css(drupal_get_path('module', 'simpletest') .'/simpletest.css', 'module');
-  drupal_add_js(drupal_get_path('module', 'simpletest') .'/simpletest.js', 'module');
+  drupal_add_js(drupal_get_path('module', 'simpletest') .'/simpletest.js');
   $header = array(
     array('data' => t('Run'), 'class' => 'simpletest_run checkbox'),
     array('data' => t('Test'), 'class' => 'simpletest_test'),
Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.218
diff -u -r1.218 locale.module
--- modules/locale/locale.module	1 Jul 2008 20:36:40 -0000	1.218
+++ modules/locale/locale.module	9 Jul 2008 18:54:36 -0000
@@ -491,6 +491,8 @@
 }
 
 /**
+ * Implementation of hook_js_alter().
+ *
  * Update JavaScript translation file, if required, and add it to the page.
  *
  * This function checks all JavaScript files currently added via drupal_add_js()
@@ -498,32 +500,32 @@
  * and Drupal.formatPlural() calls. Also refreshes the JavaScript translation
  * file if necessary, and adds it to the page.
  */
-function locale_update_js_files() {
+function locale_js_alter(&$javascript, $scope) {
   global $language;
 
+  // If there are no files set for loading, don't do anything.
+  if (!isset($javascript['file'])) {
+    return;
+  }
+
   $dir = file_create_path(variable_get('locale_js_directory', 'languages'));
   $parsed = variable_get('javascript_parsed', array());
 
   // The first three parameters are NULL in order to get an array with all
   // scopes. This is necessary to prevent recreation of JS translation files
   // when new files are added for example in the footer.
-  $javascript = drupal_add_js(NULL, NULL, NULL);
   $files = $new_files = FALSE;
-
-  foreach ($javascript as $scope) {
-    foreach ($scope as $type => $data) {
-      if ($type != 'setting' && $type != 'inline') {
-        foreach ($data as $filepath => $info) {
-          $files = TRUE;
-          if (!in_array($filepath, $parsed)) {
-            // Don't parse our own translations files.
-            if (substr($filepath, 0, strlen($dir)) != $dir) {
-              locale_inc_callback('_locale_parse_js_file', $filepath);
-              watchdog('locale', 'Parsed JavaScript file %file.', array('%file' => $filepath));
-              $parsed[] = $filepath;
-              $new_files = TRUE;
-            }
-          }
+  foreach ($javascript['file'] as $filepath => $info) {
+    // Don't parse external files.
+    if (!valid_url($filepath, TRUE)) {
+      $files = TRUE;
+      if (!in_array($filepath, $parsed)) {
+        // Don't parse our own translations files.
+        if (substr($filepath, 0, strlen($dir)) != $dir) {
+          locale_inc_callback('_locale_parse_js_file', $filepath);
+          watchdog('locale', 'Parsed JavaScript file %file.', array('%file' => $filepath));
+          $parsed[] = $filepath;
+          $new_files = TRUE;
         }
       }
     }
@@ -554,8 +556,13 @@
 
   // Add the translation JavaScript file to the page.
   if ($files && !empty($language->javascript)) {
-    drupal_add_js($dir . '/' . $language->language . '_' . $language->javascript . '.js', 'core');
+    $translation_file = $dir . '/' . $language->language . '_' . $language->javascript . '.js';
+    $javascript['file'][$translation_file] = array(
+      'cache' => FALSE,
+      'preprocess' => FALSE
+    );
   }
+  
 }
 
 // ---------------------------------------------------------------------------------
Index: modules/color/color.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/color/color.module,v
retrieving revision 1.41
diff -u -r1.41 color.module
--- modules/color/color.module	19 May 2008 19:36:41 -0000	1.41
+++ modules/color/color.module	9 Jul 2008 18:54:35 -0000
@@ -154,7 +154,7 @@
 
   // Add Farbtastic color picker.
   drupal_add_css('misc/farbtastic/farbtastic.css', 'module', 'all', FALSE);
-  drupal_add_js('misc/farbtastic/farbtastic.js');
+  drupal_add_js('misc/farbtastic/farbtastic.js', array('weight' => JS_CORE_WEIGHT));
 
   // Add custom CSS and JS.
   drupal_add_css($base . '/color.css', 'module', 'all', FALSE);
Index: modules/block/block-admin-display-form.tpl.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block-admin-display-form.tpl.php,v
retrieving revision 1.6
diff -u -r1.6 block-admin-display-form.tpl.php
--- modules/block/block-admin-display-form.tpl.php	15 May 2008 21:30:02 -0000	1.6
+++ modules/block/block-admin-display-form.tpl.php	9 Jul 2008 18:54:33 -0000
@@ -26,7 +26,7 @@
 ?>
 <?php
   // Add table javascript.
-  drupal_add_js('misc/tableheader.js');
+  drupal_add_js('misc/tableheader.js', array('weight' => JS_CORE_WEIGHT));
   drupal_add_js(drupal_get_path('module', 'block') . '/block.js');
   foreach ($block_regions as $region => $title) {
     drupal_add_tabledrag('blocks', 'match', 'sibling', 'block-region-select', 'block-region-' . $region, NULL, FALSE);
Index: includes/tests/common.test
===================================================================
RCS file: /cvs/drupal/drupal/includes/tests/common.test,v
retrieving revision 1.1
diff -u -r1.1 common.test
--- includes/tests/common.test	26 Jun 2008 21:04:17 -0000	1.1
+++ includes/tests/common.test	9 Jul 2008 18:54:33 -0000
@@ -52,3 +52,373 @@
     }
   }
 }
+
+class JavaScriptAddRemoveTestCase extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('JavaScript add/remove test'),
+      'description' => t('Tests the various options to add/remove JavaScript from a page.'),
+      'group' => t('System'),
+    );
+  }
+
+  function test_drupal_add_js() {
+    // Clear out all cached JavaScript files.
+    drupal_clear_js_cache();
+    
+    // Setup some variables needed throughout this test.
+    $jspath = file_create_path('js');
+    file_check_directory($jspath, FILE_CREATE_DIRECTORY);
+
+    // Reset the JavaScript.
+    $original_js = drupal_add_js(NULL, array(), TRUE);
+    
+    // Regex for reading relevant values from <script> tags.
+    $script_regex = '@(?:src="([^"?]+)(?:\?[^"]+)?)|>([^<]+)<\/@';
+
+    // Default JavaScript array.
+    $default_js = array(
+      'header' => array(
+        'file' => array(
+          'misc/jquery.js' => array(
+            'weight' => -100,
+            'cache' => TRUE,
+            'aggregate' => TRUE,
+            'attributes' => array('type' => 'text/javascript'),
+          ),
+          'misc/drupal.js' => array(
+            'weight' => -99,
+            'cache' => TRUE,
+            'aggregate' => TRUE,
+            'attributes' => array('type' => 'text/javascript'),
+          ),
+        ),
+        'setting' => array(
+          'basePath' => base_path(),
+        ),
+        'inline' => array()
+      )
+    );
+    
+    // Second time - make sure it retains the defaults.
+    $this->assertIdentical(drupal_add_js(), $default_js, t('Test drupal_add_js() without parameters.'));
+
+    // Try adding a duplicate file.
+    drupal_add_js('misc/jquery.js');
+    $this->assertIdentical(drupal_add_js(), $default_js, t('Adding a duplicate file was ignored.'));
+
+    // Try the reset parameter.
+    drupal_add_js('misc/'. $this->randomName(8) . '.js');
+    drupal_add_js(NULL, array(), TRUE);
+    $this->assertIdentical(drupal_add_js(), $default_js, t('Reset actually reset the JavaScript.'));
+
+    // Test the weight system with files.
+    $weights = array(1, 10, -10);
+    $files = array();
+    $sort = array();
+    foreach ($weights as $weight) {
+      $js_file = 'misc/' . $this->randomName(8) . '.js';
+      $files[$js_file] = array(
+        'weight' => $weight,
+        'cache' => TRUE,
+        'aggregate' => TRUE,
+        'remove' => FALSE,
+        'attributes' => array('type' => 'text/javascript'),
+      );
+      $sort[$js_file] = $weight;
+    }
+    
+    foreach ($files as $path => $options) {
+      drupal_add_js($path, array('weight' => $options['weight']));
+    }
+    array_multisort($sort, SORT_NUMERIC, $files);
+
+    // Make a copy of the default JS.
+    $new_js = $default_js;
+    $new_js['header']['file'] += $files;
+    $this->assertIdentical(drupal_add_js(), $new_js, t('The weights system works with files.'));
+    
+    // Test deleting with $options['remove'] = TRUE.
+    $paths = array_keys($files);
+    $new_js = drupal_add_js($paths[0], array('remove' => TRUE));
+    $this->assertFalse(isset($new_js['header']['file'][$paths[0]]), t('The JavaScript was deleted from the array with $options[\'remove\'] = TRUE.'));
+
+    // Test deleting with drupal_remove_js().
+    $paths = array_keys($files);
+    $new_js = drupal_remove_js($paths[1]);
+    $this->assertFalse(isset($new_js['header']['file'][$paths[1]]), t('The JavaScript was deleted from the array with drupal_remove_js().'));
+
+    // Test resetting.  Although I can't trigger the if (empty($javascript)), I can trigger the $reset.
+    drupal_add_js(NULL, array(), TRUE);
+    $this->assertIdentical(drupal_add_js(), $default_js, t('The JavaScript was reset.'));
+    
+    // Test deleting without adding first.
+    $js_name = $this->randomName();
+    drupal_remove_js($js_name);
+    $final_js = drupal_add_js();
+    $this->assertFalse(isset($final_js['file'][$js_name]), t('A file that has not been added, but removed, doesn\'t show up.'));
+    
+    // Test settings.
+    $new_js = drupal_add_js(NULL, array(), TRUE);
+    
+    $settings = array();
+    $settings['boo'] = $this->randomName();
+    $settings['test'][$this->randomName()] = $this->randomName();
+    $settings[$this->randomName()] = array($this->randomName(), $this->randomName());
+    $settings[$this->randomName()] = array('test' => $this->randomName(), 'test2' => $this->randomName());
+    $new_js['header']['setting'] = array_merge_recursive($new_js['header']['setting'], $settings);
+    drupal_add_js($settings, 'setting');
+    $this->assertIdentical(drupal_add_js(), $new_js, t('Added settings with type => \'setting\'.'));
+
+    // Test merging of additional settings.
+    $settings = array();
+    $settings['boo'] = $this->randomName(); // Should create an array with existing value
+    $settings['test'][$this->randomName()] = $this->randomName(); // Should create a 2-element array under ['test'].
+    $settings[$this->randomName()] = array($this->randomName(), $this->randomName()); // Should add nicely.
+    $new_js['header']['setting'] = array_merge_recursive($new_js['header']['setting'], $settings);
+    $final_js = drupal_add_js($settings, 'setting');
+    $verified = count($final_js['header']['setting']['boo']) == 2 &&
+                count($final_js['header']['setting']['test']) == 2;
+    $this->assertTrue($verified, t('Setting arrays merge correctly.'));
+    $this->assertIdentical($final_js, $new_js, t('Additional settings are merged in '));
+
+    // Test inline.
+    $new_js = drupal_add_js(NULL, array(), TRUE);
+    drupal_add_js("$(function() { $('test').hide(); });", 'inline');
+    $new_js['header']['inline']["$(function() { $('test').hide(); });"] = array(
+      'weight' => JS_MODULE_WEIGHT,
+      'cache' => TRUE,
+      'aggregate' => TRUE,
+      'remove' => FALSE,
+      'attributes' => array('type' => 'text/javascript'),
+    );
+    $this->assertIdentical(drupal_add_js(), $new_js, t('Added inline JavaScript.'));
+    
+    // Test externally available JavaScript without caching.
+    drupal_clear_js_cache();
+    drupal_add_js(NULL, array(), TRUE);
+    $filepath = url('misc/teaser.js', array('absolute' => TRUE));
+    $filepath = str_replace('localhost', '127.0.0.1', $filepath);
+    $new_js = drupal_add_js($filepath, array('cache' => FALSE));
+    $final_js = drupal_get_js('header');
+    $this->assertTrue(strpos($final_js, $filepath) !== FALSE, t('Add an available external JavaScript file without caching.'));
+    
+    // Test externally unavailable JavaScript without caching.
+    drupal_clear_js_cache();
+    drupal_add_js(NULL, array(), TRUE);
+    $filepath = url('misc/no_real_file.js', array('absolute' => TRUE));
+    $filepath = str_replace('localhost', '127.0.0.1', $filepath);
+    $new_js = drupal_add_js($filepath, array('cache' => FALSE));
+    $final_js = drupal_get_js('header');
+    $this->assertTrue(strpos($final_js, $filepath) !== FALSE, t('Add an unavailable external JavaScript file without caching.'));
+    
+    // Test externally available JavaScript with caching.
+    // Caching is handled in drupal_get_js(), though it should probably be in a 
+    // separate hook_js_alter(), so that's why drupal_get_js() is neccesary to
+    // check this.
+    // We only try to cache one file as caching with multiple files,
+    variable_set('preprocess_js', TRUE);
+    drupal_clear_js_cache();
+    drupal_add_js(NULL, array(), TRUE);
+    $filepath = url('misc/teaser.js', array('absolute' => TRUE));
+    $filepath = str_replace('localhost', '127.0.0.1', $filepath);
+    $new_js = drupal_add_js($filepath);
+    $final_js = drupal_get_js('header');
+    $local_path = $jspath . '/' . md5($filepath) . '.js';
+
+    // We should only end up with one JS file for download as aggregation 
+    // happened. We should also see a cached version of the file.
+    $this->assertIdentical(substr_count($final_js, '.js'), 1, t('Add an available external JavaScript file with caching.'));
+    $this->assertTrue(file_exists($local_path), t('Cache the external JavaScript.'));
+
+    // Test externally unavailable JavaScript with caching.
+    variable_set('preprocess_js', TRUE);
+    drupal_clear_js_cache();
+    drupal_add_js(NULL, array(), TRUE);
+    $filepath = url('misc/no_real_file.js', array('absolute' => TRUE));
+    $filepath = str_replace('localhost', '127.0.0.1', $filepath);
+    $new_js = drupal_add_js($filepath);
+    $final_js = drupal_get_js('header');
+    $this->assertTrue(strpos($final_js, md5($filepath) . '.js') === FALSE, t('Caching an unavailable external JavaScript file doesn\'t cache it.'));
+    $this->assertTrue(strpos($final_js, $filepath) !== FALSE, t('Adding an unavailable external JavaScript file with caching adds it as a script.'));
+
+    // Test externally available JavaScript with caching, but with preprocessing
+    // disabled.
+    variable_set('preprocess_js', FALSE);
+    drupal_clear_js_cache();
+    drupal_add_js(NULL, array(), TRUE);
+    $filepath = url('misc/teaser.js', array('absolute' => TRUE));
+    $filepath = str_replace('localhost', '127.0.0.1', $filepath);
+    $new_js = drupal_add_js($filepath);
+    $final_js = drupal_get_js('header');
+    $this->assertTrue(strpos($final_js, md5($filepath) . '.js') !== FALSE, t('Add an available external JavaScript file with caching, but without preprocessing.'));
+    
+    // Test externally unavailable JavaScript with caching, but with 
+    // preprocessing disabled.
+    variable_set('preprocess_js', FALSE);
+    drupal_clear_js_cache();
+    drupal_add_js(NULL, array(), TRUE);
+    $filepath = url('misc/no_real_file.js', array('absolute' => TRUE));
+    $filepath = str_replace('localhost', '127.0.0.1', $filepath);
+    $new_js = drupal_add_js($filepath);
+    $final_js = drupal_get_js('header');
+    $this->assertTrue(strpos($final_js, md5($filepath) . '.js') === FALSE, t('Caching an unavailable external JavaScript file doesn\'t cache it.'));
+    $this->assertTrue(strpos($final_js, $filepath) !== FALSE, t('Adding an unavailable external JavaScript file with caching adds it as a script.'));
+    
+    // Test scoping.
+    // Add default 'header' JS.
+    variable_set('preprocess_js', FALSE);
+    drupal_clear_js_cache();
+    drupal_add_js(NULL, array(), TRUE);
+    drupal_add_js("$(function() { $('header').hide(); });", 'inline');
+    $header_settings = array(
+      $this->randomName() => $this->randomName(),
+      $this->randomName() => array($this->randomName(), $this->randomName()),
+      $this->randomName() => array('test' => $this->randomName(), 'test2' => $this->randomName())
+    );
+    drupal_add_js($header_settings, 'setting');
+    
+    // Add 'footer' JS.
+    drupal_add_js('misc/batch.js', array('scope' => 'footer'));
+    drupal_add_js("$(function() { $('footer').hide(); });", array('type' => 'inline', 'scope' => 'footer'));
+    $footer_settings = array(
+      $this->randomName() => array('footer' => $this->randomName(), 'test3' => $this->randomName())
+    );
+    drupal_add_js($footer_settings, array('type' => 'setting', 'scope' => 'footer'));
+    
+    // Add 'other' JS.
+    drupal_add_js('misc/collapse.js', array('scope' => 'other'));
+    drupal_add_js("$(function() { $('other').hide(); });", array('type' => 'inline', 'scope' => 'other'));
+    $other_settings = array(
+      $this->randomName() => array('other' => $this->randomName(), 'test4' => $this->randomName())
+    );
+    drupal_add_js($other_settings, array('type' => 'setting', 'scope' => 'other'));
+    
+    // Check that 'header' only contains 'header' JS.
+    // We should only see 4 script tags and they should match our input exactly.
+    $header_js = drupal_get_js('header');
+    $matches = array();
+    preg_match_all($script_regex, $header_js, $matches);
+    $h_settings_js = drupal_to_js($header_settings);
+    $verified = (count($matches[0]) == 4) &&
+                ($matches[1][0] == url('misc/jquery.js')) &&
+                ($matches[1][1] == url('misc/drupal.js')) &&
+                strpos($matches[2][2], substr($h_settings_js, 1, strlen($h_settings_js) - 2)) !== FALSE &&
+                ($matches[2][3] == "$(function() { $('header').hide(); });");
+    $this->assertTrue($verified, t('Scripts attached to the "header" section are only retrieved when requested.'));
+    
+    // Check that 'footer' only contains 'footer' JS.
+    $footer_js = drupal_get_js('footer');
+    $matches = array();
+    preg_match_all($script_regex, $footer_js, $matches);
+    $f_settings_js = drupal_to_js($footer_settings);
+    $verified = (count($matches[0]) == 3) &&
+                ($matches[1][0] == url('misc/batch.js')) &&
+                strpos($matches[2][1], substr($f_settings_js, 1, strlen($f_settings_js) - 2)) !== FALSE &&
+                ($matches[2][2] == "$(function() { $('footer').hide(); });");
+    $this->assertTrue($verified, t('Scripts attached to the "footer" section are only retrieved when requested.'));
+    
+    // Check that 'other' only contains 'other' JS.
+    $other_js = drupal_get_js('other');
+    $matches = array();
+    preg_match_all($script_regex, $other_js, $matches);
+    $o_settings_js = drupal_to_js($other_settings);
+    $verified = (count($matches[0]) == 3) &&
+                ($matches[1][0] == url('misc/collapse.js')) &&
+                strpos($matches[2][1], substr($o_settings_js, 1, strlen($o_settings_js) - 2)) !== FALSE &&
+                ($matches[2][2] == "$(function() { $('other').hide(); });");
+    $this->assertTrue($verified, t('Scripts attached to the "other" section are only retrieved when requested.'));
+
+    // Test complicated combination of scripts
+    variable_set('preprocess_js', TRUE);
+    drupal_clear_js_cache();
+    drupal_add_js(NULL, array(), TRUE);
+    // Add external file to cache and aggregate.
+    drupal_add_js(url('misc/form.js', array('absolute' => TRUE)));
+    // Add regular file without caching or aggregation.
+    drupal_add_js('misc/batch.js', array('cache' => FALSE, 'aggregate' => FALSE));
+    // Add setting with caching and aggregation.
+    $settings = array(
+      $this->randomName() => array('test56' => $this->randomName(), 'test3' => $this->randomName())
+    );
+    drupal_add_js($settings, 'setting');
+    // Add inline without caching or aggregation.
+    drupal_add_js("$(function() { $('blah').hide(); });", array('type' => 'inline', 'cache' => FALSE, 'aggregate' => FALSE));
+    // Add external file without aggregation.
+    $filepath = $jspath . '/test1js.js';
+    file_save_data('TESTSTRING', $filepath, FILE_EXISTS_REPLACE);
+    $fileurl = url($filepath, array('absolute' => TRUE));
+    $js = drupal_add_js($fileurl, array('aggregate' => FALSE));
+    $final_js = drupal_get_js('header');
+    $matches = array();
+    preg_match_all($script_regex, $final_js, $matches);
+    // Check that files were added properly.
+    $verified = (count($matches[0]) == 5) &&
+                (strpos($matches[1][2], md5($fileurl)) !== FALSE) &&
+                ($matches[1][1] == url('misc/batch.js'));
+    // Check that inline JS was added properly.
+    $verified = $verified && $matches[2][4] == "$(function() { $('blah').hide(); });";
+    // Check that settings were added properly.
+    $settings_str = drupal_to_js($settings);
+    $verified = $verified && (strpos($matches[2][3], substr($settings_str, 1, strlen($settings_str) - 2)) !== FALSE);
+    // Check that files were aggregated correctly
+    $aggr_file = str_replace(base_path(), '', $matches[1][0]);
+    $verified = $verified && file_exists($aggr_file);
+    $aggregated_js = file_get_contents($aggr_file);
+    $verified = $verified && (strpos($aggregated_js, 'drupal.js') !== FALSE) &&
+                (strpos($aggregated_js, 'jquery.js') !== FALSE) &&
+                (strpos($aggregated_js, 'teaser.js') === FALSE) &&
+                (strpos($aggregated_js, 'form.js') !== FALSE);
+    // Check cached files
+    $verified = $verified && file_exists($jspath . '/' . md5(url('misc/form.js', array('absolute' => TRUE))) . '.js');
+    $this->assertTrue($verified, t("Complicated JS listings are handled correctly."));
+
+    // Test caching/aggregation for when a JS file suddenly becomes available
+    // after it hadn't been available.
+    variable_set('preprocess_js', TRUE);
+    drupal_clear_js_cache();
+    drupal_add_js(NULL, array(), TRUE);
+    
+    $filepath = $jspath . '/tempjs.js';
+    $fileurl = url($filepath, array('absolute' => TRUE));
+    $js = drupal_add_js($fileurl);
+    $final_js = drupal_get_js('header');
+    // Check defaults and additional script being handled correctly.
+    $matches = array();
+    preg_match_all($script_regex, $final_js, $matches);
+    $verified = (count($matches[0]) == 3) &&
+                ($matches[1][1] == $fileurl) &&
+                !empty($matches[1][0]) &&
+                strpos($matches[2][2], 'Drupal.settings') !== FALSE;
+   
+    // Add the missing file in and check that it was cached and aggregated.
+    $tempjs = file_save_data('TESTSTRING', $filepath, FILE_EXISTS_REPLACE);
+    $verified = $verified && ($tempjs !== FALSE);
+    $final_js = drupal_get_js('header');
+    $matches = array();
+    preg_match_all($script_regex, $final_js, $matches);
+    
+    // Check that caching worked correctly.
+    $cachedpath = $jspath . '/' . md5($fileurl) . '.js';
+    $verified = $verified && file_exists($cachedpath);
+
+    // Check that aggregation worked correctly
+    $aggregatedpath = explode('/', $matches[1][0]);
+    $aggregatedpath = $aggregatedpath[count($aggregatedpath) - 1];
+    $aggregatedpath = $jspath . '/' . $aggregatedpath;
+    $aggregatedpath = str_replace(base_path(), '', $matches[1][0]);
+    $verified = $verified && file_exists($aggregatedpath);
+    $verified = $verified && strpos(file_get_contents($aggregatedpath), 'TESTSTRING') !== FALSE;
+    $this->assertTrue($verified, t("A file that was unavailable is handled correctly when it becomes available."));
+    
+    // Test caching.
+    $new_js = drupal_add_js(NULL, array(), TRUE);
+    $file = $this->randomName();
+    $new_js = drupal_add_js($file, array('cache' => FALSE));
+    $this->assertFalse($new_js['files'][$file]['cache'], t('The setting to turn caching off went through.'));
+    $this->assertFalse($new_js['files'][$file]['preprocess'], t('The setting to turn caching off, in turn turning preprocess off, went through.'));
+  }
+}
\ No newline at end of file
Index: modules/node/node.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.admin.inc,v
retrieving revision 1.22
diff -u -r1.22 node.admin.inc
--- modules/node/node.admin.inc	14 Apr 2008 17:48:38 -0000	1.22
+++ modules/node/node.admin.inc	9 Jul 2008 18:54:38 -0000
@@ -259,7 +259,7 @@
     $form['filters']['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset'));
   }
 
-  drupal_add_js('misc/form.js', 'core');
+  drupal_add_js('misc/form.js', array('weight' => JS_CORE_WEIGHT));
 
   return $form;
 }
Index: install.php
===================================================================
RCS file: /cvs/drupal/drupal/install.php,v
retrieving revision 1.122
diff -u -r1.122 install.php
--- install.php	3 Jul 2008 17:57:03 -0000	1.122
+++ install.php	9 Jul 2008 18:54:14 -0000
@@ -719,7 +719,7 @@
 
       // Add JavaScript validation.
       _user_password_dynamic_validation();
-      drupal_add_js(drupal_get_path('module', 'system') . '/system.js', 'module');
+      drupal_add_js(drupal_get_path('module', 'system') . '/system.js');
       // We add these strings as settings because JavaScript translation does not
       // work on install time.
       drupal_add_js(array('copyFieldValue' => array('edit-site-mail' => array('edit-account-mail')), 'cleanURL' => array('success' => st('Your server has been successfully tested to support this feature.'), 'failure' => st('Your system configuration does not currently support this feature. The <a href="http://drupal.org/node/15365">handbook page on Clean URLs</a> has additional troubleshooting information.'), 'testing' => st('Testing clean URLs...'))), 'setting');
