Index: includes/theme.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/theme.inc,v retrieving revision 1.426 diff -u -r1.426 theme.inc --- includes/theme.inc 6 Jun 2008 01:50:20 -0000 1.426 +++ includes/theme.inc 13 Jun 2008 19:52:12 -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; @@ -1284,7 +1284,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'); @@ -1401,7 +1401,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/common.test =================================================================== RCS file: /cvs/drupal/drupal/includes/common.test,v retrieving revision 1.1 diff -u -r1.1 common.test --- includes/common.test 9 Jun 2008 08:11:44 -0000 1.1 +++ includes/common.test 13 Jun 2008 19:52:11 -0000 @@ -52,3 +52,370 @@ } } } + +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 \n"; + if (!empty($data)) { + $renderable_js[] = array( + 'type' => 'setting', + 'data' => $data, + ); + } break; case 'inline': - foreach ($data as $info) { - $output .= '\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] .= '\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 .= '' . "\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 .= '\n"; + break; + case 'inline': + $output .= '' . $script['data'] . "\n"; + break; + case 'file': + $output .= '\n"; + } + } return $output; } @@ -2200,7 +2435,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' => -20)); $js_added = TRUE; } @@ -2220,15 +2455,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. @@ -2236,12 +2471,16 @@ 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; + } } // Create the JS file. @@ -2252,10 +2491,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()); } @@ -2263,6 +2528,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() @@ -2277,6 +2547,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. @@ -3019,6 +3290,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 13 Jun 2008 19:52:13 -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.909 diff -u -r1.909 user.module --- modules/user/user.module 26 May 2008 17:12:55 -0000 1.909 +++ modules/user/user.module 13 Jun 2008 19:52:14 -0000 @@ -2111,7 +2111,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.76 diff -u -r1.76 system.admin.inc --- modules/system/system.admin.inc 10 May 2008 07:32:02 -0000 1.76 +++ modules/system/system.admin.inc 13 Jun 2008 19:52:13 -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'] .= ' ' . 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 handbook page on Clean URLs has additional troubleshooting information.', array('@handbook' => 'http://drupal.org/node/15365')) . ''; Index: modules/simpletest/simpletest.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.module,v retrieving revision 1.3 diff -u -r1.3 simpletest.module --- modules/simpletest/simpletest.module 10 May 2008 07:46:22 -0000 1.3 +++ modules/simpletest/simpletest.module 13 Jun 2008 19:52:12 -0000 @@ -81,7 +81,7 @@ function simpletest_entrypoint() { simpletest_load(); 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', array('weight' => JS_MODULE_WEIGHT)); $output = drupal_get_form('simpletest_overview_form'); if (simpletest_running_output()) { Index: modules/locale/locale.module =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v retrieving revision 1.217 diff -u -r1.217 locale.module --- modules/locale/locale.module 23 Apr 2008 20:01:52 -0000 1.217 +++ modules/locale/locale.module 13 Jun 2008 19:52:12 -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 13 Jun 2008 19:52:12 -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 13 Jun 2008 19:52:12 -0000 @@ -26,7 +26,7 @@ ?> 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: 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 13 Jun 2008 19:52:12 -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.119 diff -u -r1.119 install.php --- install.php 26 May 2008 17:12:54 -0000 1.119 +++ install.php 13 Jun 2008 19:52:10 -0000 @@ -711,10 +711,20 @@ // 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 handbook page on Clean URLs has additional troubleshooting information.'), 'testing' => st('Testing clean URLs...'))), 'setting'); + $js_settings = 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 handbook page on Clean URLs has additional troubleshooting information.'), + 'testing' => st('Testing clean URLs...') + ) + ); + drupal_add_js($js_settings, 'setting'); drupal_add_js(' // Global Killswitch if (Drupal.jsEnabled) {