diff --git a/includes/common.inc b/includes/common.inc
index 3e8d6d9..ba2df94 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -70,8 +70,7 @@ define('CSS_DEFAULT', 0);
define('CSS_THEME', 100);
/**
- * The default group for JavaScript libraries or jQuery plugins added to the
- * page.
+ * The default group for JavaScript and jQuery libraries added to the page.
*/
define('JS_LIBRARY', -100);
@@ -4288,79 +4287,13 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
}
}
- // Sort the JavaScript so that it appears in the correct order.
- uasort($items, 'drupal_sort_css_js');
-
- // In Drupal 8, there's a JS_SETTING group for making setting variables
- // appear last after libraries have loaded. In Drupal 7, this is forced
- // without that group. We do not use the $key => $item type of iteration,
- // because PHP uses an internal array pointer for that, and we're modifying
- // the array order inside the loop.
- foreach (array_keys($items) as $key) {
- if ($items[$key]['type'] == 'setting') {
- $item = $items[$key];
- unset($items[$key]);
- $items[$key] = $item;
- }
- }
-
- // Provide the page with information about the individual JavaScript files
- // used, information not otherwise available when aggregation is enabled.
- $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($items), 1);
- unset($setting['ajaxPageState']['js']['settings']);
- drupal_add_js($setting, 'setting');
-
- // If we're outputting the header scope, then this might be the final time
- // that drupal_get_js() is running, so add the setting to this output as well
- // as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's
- // because drupal_get_js() was intentionally passed a $javascript argument
- // stripped of settings, potentially in order to override how settings get
- // output, so in this case, do not add the setting to this output.
- if ($scope == 'header' && isset($items['settings'])) {
- $items['settings']['data'][] = $setting;
- }
-
- // Render the HTML needed to load the JavaScript.
- $elements = array(
- '#type' => 'scripts',
- '#items' => $items,
- );
-
- return drupal_render($elements);
-}
-
-/**
- * #pre_render callback to add the elements needed for JavaScript tags to be rendered.
- *
- * This function evaluates the aggregation enabled/disabled condition on a group
- * by group basis by testing whether an aggregate file has been made for the
- * group rather than by testing the site-wide aggregation setting. This allows
- * this function to work correctly even if modules have implemented custom
- * logic for grouping and aggregating files.
- *
- * @param $element
- * A render array containing:
- * - #items: The JavaScript items as returned by drupal_add_js() and
- * altered by drupal_get_js().
- * - #group_callback: A function to call to group #items. Following
- * this function, #aggregate_callback is called to aggregate items within
- * the same group into a single file.
- * - #aggregate_callback: A function to call to aggregate the items within
- * the groups arranged by the #group_callback function.
- *
- * @return
- * A render array that will render to a string of JavaScript tags.
- *
- * @see drupal_get_js()
- */
-function drupal_pre_render_scripts($elements) {
- // Group and aggregate the items.
- if (isset($elements['#group_callback'])) {
- $elements['#groups'] = $elements['#group_callback']($elements['#items']);
- }
- if (isset($elements['#aggregate_callback'])) {
- $elements['#aggregate_callback']($elements['#groups']);
- }
+ $output = '';
+ // The index counter is used to keep aggregated and non-aggregated files in
+ // order by weight.
+ $index = 1;
+ $processed = array();
+ $files = array();
+ $preprocess_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
// A dummy query-string is added to filenames, to gain control over
// browser-caching. The string changes on every update or full cache
@@ -4380,8 +4313,27 @@ function drupal_pre_render_scripts($elements) {
// third-party code might require the use of a different query string.
$js_version_string = variable_get('drupal_js_version_query_string', 'v=');
- // Defaults for each SCRIPT element.
- $element_defaults = array(
+ // Sort the JavaScript so that it appears in the correct order.
+ uasort($items, 'drupal_sort_css_js');
+
+ // Provide the page with information about the individual JavaScript files
+ // used, information not otherwise available when aggregation is enabled.
+ $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($items), 1);
+ unset($setting['ajaxPageState']['js']['settings']);
+ drupal_add_js($setting, 'setting');
+
+ // If we're outputting the header scope, then this might be the final time
+ // that drupal_get_js() is running, so add the setting to this output as well
+ // as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's
+ // because drupal_get_js() was intentionally passed a $javascript argument
+ // stripped off settings, potentially in order to override how settings get
+ // output, so in this case, do not add the setting to this output.
+ if ($scope == 'header' && isset($items['settings'])) {
+ $items['settings']['data'][] = $setting;
+ }
+
+ // Loop through the JavaScript to construct the rendered output.
+ $element = array(
'#type' => 'html_tag',
'#tag' => 'script',
'#value' => '',
@@ -4389,173 +4341,108 @@ function drupal_pre_render_scripts($elements) {
'type' => 'text/javascript',
),
);
+ foreach ($items as $item) {
+ $query_string = empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];
- // Loop through each group.
- foreach ($elements['#groups'] as $group) {
- // If a group of files has been aggregated into a single file,
- // $group['data'] contains the URI of the aggregate file. Add a single
- // script element for this file.
- if ($group['type'] == 'file' && isset($group['data'])) {
- $element = $element_defaults;
- $element['#attributes']['src'] = file_create_url($group['data']);
- $element['#browsers'] = $group['browsers'];
- $elements[] = $element;
- }
- // For non-file types, and non-aggregated files, add a script element per
- // item.
- else {
- foreach ($group['items'] as $item) {
- // Element properties that do not depend on item type.
- $element = $element_defaults;
- if (!empty($item['defer'])) {
- $element['#attributes']['defer'] = 'defer';
- }
- $element['#browsers'] = $item['browsers'];
+ $js_element = $element;
- // Element properties that depend on item type.
- switch ($item['type']) {
- case 'setting':
- $element['#value_prefix'] = $embed_prefix;
- $element['#value'] = 'jQuery.extend(Drupal.settings, ' . drupal_json_encode(drupal_array_merge_deep_array($item['data'])) . ");";
- $element['#value_suffix'] = $embed_suffix;
- break;
+ // Ensure browser defaults are populated.
+ $item['browsers'] += array(
+ 'IE' => TRUE,
+ '!IE' => TRUE,
+ );
- case 'inline':
- $element['#value_prefix'] = $embed_prefix;
- $element['#value'] = $item['data'];
- $element['#value_suffix'] = $embed_suffix;
- break;
+ // Generate a unique key that represents browsers so they can be grouped.
+ $browsers_key = ($item['browsers']['IE'] ? drupal_html_class($item['browsers']['IE']) : '0') . '_' . ($item['browsers']['!IE'] ? '1' : '0');
- case 'file':
- $query_string = empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];
- $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?';
- $element['#attributes']['src'] = file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME);
- break;
+ switch ($item['type']) {
+ case 'setting':
+ $js_element['#value_prefix'] = $embed_prefix;
+ $js_element['#value'] = 'jQuery.extend(Drupal.settings, ' . drupal_json_encode(drupal_array_merge_deep_array($item['data'])) . ");";
+ $js_element['#value_suffix'] = $embed_suffix;
+ $output .= drupal_render($js_element);
+ break;
- case 'external':
- $element['#attributes']['src'] = $item['data'];
- break;
+ case 'inline':
+ if ($item['defer']) {
+ $js_element['#attributes']['defer'] = 'defer';
}
+ $js_element['#value_prefix'] = $embed_prefix;
+ $js_element['#value'] = $item['data'];
+ $js_element['#value_suffix'] = $embed_suffix;
+ $js_element['#browsers'] = $item['browsers'];
+ $processed[$index++] = drupal_render($js_element);
+ break;
- $elements[] = $element;
- }
- }
- }
-
- return $elements;
-}
-
-/**
- * Default callback to group JavaScript items.
- *
- * This function arranges the JavaScript items that are in the #items property
- * of the scripts element into groups. When aggregation is enabled, files within
- * a group are aggregated into a single file, significantly improving page
- * loading performance by minimizing network traffic overhead.
- *
- * This function puts multiple items into the same group if they are groupable
- * and if they are for the same browsers. Items of the 'file' type are groupable
- * if their 'preprocess' flag is TRUE. Items of the 'inline', 'settings', or
- * 'external' type are not groupable.
- *
- * This function also ensures that the process of grouping items does not change
- * their relative order. This requirement may result in multiple groups for the
- * same type and browsers, if needed to accommodate other items in
- * between.
- *
- * @param $javascript
- * An array of JavaScript items, as returned by drupal_add_js(), but after
- * alteration performed by drupal_get_js().
- *
- * @return
- * An array of JavaScript groups. Each group contains the same keys (e.g.,
- * 'data', etc.) as a JavaScript item from the $javascript parameter, with the
- * value of each key applying to the group as a whole. Each group also
- * contains an 'items' key, which is the subset of items from $javascript that
- * are in the group.
- *
- * @see drupal_pre_render_scripts()
- */
-function drupal_group_js($javascript) {
- $groups = array();
- // If a group can contain multiple items, we track the information that must
- // be the same for each item in the group, so that when we iterate the next
- // item, we can determine if it can be put into the current group, or if a
- // new group needs to be made for it.
- $current_group_keys = NULL;
- $index = -1;
- foreach ($javascript as $item) {
- // The browsers for which the JavaScript item needs to be loaded is part of
- // the information that determines when a new group is needed, but the order
- // of keys in the array doesn't matter, and we don't want a new group if all
- // that's different is that order.
- ksort($item['browsers']);
-
- switch ($item['type']) {
case 'file':
- // Group file items if their 'preprocess' flag is TRUE.
- // Help ensure maximum reuse of aggregate files by only grouping
- // together items that share the same 'group' value and 'every_page'
- // flag. See drupal_add_js() for details about that.
- $group_keys = $item['preprocess'] ? array($item['type'], $item['group'], $item['every_page'], $item['browsers']) : FALSE;
+ if (!$item['preprocess'] || !$preprocess_js) {
+ if ($item['defer']) {
+ $js_element['#attributes']['defer'] = 'defer';
+ }
+ $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?';
+ $js_element['#attributes']['src'] = file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME);
+ $js_element['#browsers'] = $item['browsers'];
+ // If item renders in all browsers, then there is no need for
+ // conditional comments. Increase and use the index as the key.
+ if ($item['browsers']['IE'] === TRUE && $item['browsers']['!IE']) {
+ $processed[$index++] = drupal_render($js_element);
+ }
+ // If item renders for specific browsers, group by browsers key and
+ // append each item so they are grouped properly.
+ else {
+ $processed[$browsers_key] = (!empty($processed[$browsers_key]) ? $processed[$browsers_key] : '') . drupal_render($js_element);
+ }
+ }
+ else {
+ // By increasing the index for each aggregated file, we maintain
+ // the relative ordering of JS by weight. We also set the key such
+ // that groups are split by items sharing the same 'group' value
+ // 'every_page' flag and browser conditions. While this potentially
+ // results in more aggregate files, it helps make each one more
+ // reusable across a site visit, leading to better front-end
+ // performance of a website as a whole. See drupal_add_js() for more
+ // details.
+ $key = $item['type'] . '_' . $item['every_page'] . '_' . $browsers_key . '_' . $index;
+ $processed[$key] = '';
+ $files[$key][$item['data']] = $item;
+ }
break;
case 'external':
- case 'setting':
- case 'inline':
- // Do not group external, settings, and inline items.
- $group_keys = FALSE;
+ // Preprocessing for external JavaScript files is ignored.
+ if ($item['defer']) {
+ $js_element['#attributes']['defer'] = 'defer';
+ }
+ $js_element['#attributes']['src'] = $item['data'];
+ $js_element['#browsers'] = $item['browsers'];
+ $processed[$index++] = drupal_render($js_element);
break;
}
-
- // If the group keys don't match the most recent group we're working with,
- // then a new group must be made.
- if ($group_keys !== $current_group_keys) {
- $index++;
- // Initialize the new group with the same properties as the first item
- // being placed into it. The item's 'data' and 'weight' properties are
- // unique to the item and should not be carried over to the group.
- $groups[$index] = $item;
- unset($groups[$index]['data'], $groups[$index]['weight']);
- $groups[$index]['items'] = array();
- $current_group_keys = $group_keys ? $group_keys : NULL;
- }
-
- // Add the item to the current group.
- $groups[$index]['items'][] = $item;
}
- return $groups;
-}
-
-/**
- * Default callback to aggregate JavaScript files.
- *
- * Having the browser load fewer JavaScript files results in much faster page
- * loads than when it loads many small files. This function aggregates files
- * within the same group into a single file unless the site-wide setting to do
- * so is disabled (commonly the case during site development). To optimize
- * download, it also compresses the aggregate files by removing comments,
- * whitespace, and other unnecessary content.
- *
- * @param $js_groups
- * An array of JavaScript groups as returned by drupal_group_js(). For each
- * group that is aggregated, this function sets the value of the group's
- * 'data' key to the URI of the aggregate file.
- *
- * @see drupal_group_js()
- * @see drupal_pre_render_scripts()
- */
-function drupal_aggregate_js(&$js_groups) {
- // Only aggregate when the site is configured to do so, and not during an
- // update.
- if (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')) {
- foreach ($js_groups as $key => $group) {
- if ($group['type'] == 'file' && $group['preprocess']) {
- $js_groups[$key]['data'] = drupal_build_js_cache($group['items']);
+ // Aggregate any remaining JS files that haven't already been output.
+ if ($preprocess_js && count($files) > 0) {
+ foreach ($files as $key => $file_set) {
+ $uri = drupal_build_js_cache($file_set);
+ // Only include the file if was written successfully. Errors are logged
+ // using watchdog.
+ if ($uri) {
+ $preprocess_file = file_create_url($uri);
+ $js_element = $element;
+ $js_element['#attributes']['src'] = $preprocess_file;
+ // The key contains browsers array as part of it's unique value, use
+ // the first file set's value for this preprocessed version.
+ $file_set_values = array_values($file_set);
+ $item = array_shift($file_set_values);
+ $js_element['#browsers'] = $item['browsers'];
+ $processed[$key] = drupal_render($js_element);
}
}
}
+
+ // 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.
+ return implode('', $processed) . $output;
}
/**
@@ -5095,10 +4982,10 @@ function drupal_build_js_cache($files) {
if (empty($uri) || !file_exists($uri)) {
// Build aggregate JS file.
- foreach ($files as $info) {
+ foreach ($files as $path => $info) {
if ($info['preprocess']) {
// Append a ';' and a newline after each JS file to prevent them from running together.
- $contents .= file_get_contents($info['data']) . ";\n";
+ $contents .= file_get_contents($path) . ";\n";
}
}
// Prefix filename to prevent blocking by firewalls which reject files
diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test
index fc526cb..64131f3 100644
--- a/modules/simpletest/tests/common.test
+++ b/modules/simpletest/tests/common.test
@@ -1456,30 +1456,96 @@ class JavaScriptTestCase extends DrupalWebTestCase {
drupal_add_js('misc/authorize.js', array('every_page' => TRUE));
drupal_add_js('misc/autocomplete.js');
drupal_add_js('misc/batch.js', array('every_page' => TRUE));
+ drupal_add_js('misc/collapse.js', array('browsers' => array(
+ 'IE' => 'lt IE 8',
+ '!IE' => FALSE,
+ )));
+ drupal_add_js('misc/states.js', array('browsers' => array(
+ 'IE' => 'lte IE 6',
+ '!IE' => FALSE,
+ )));
+ drupal_add_js('misc/textarea.js', array('browsers' => array(
+ 'IE' => 'gt IE 7',
+ '!IE' => TRUE,
+ )));
+ drupal_add_js('misc/progress.js', array('browsers' => array(
+ 'IE' => 'lt IE 8',
+ '!IE' => FALSE,
+ )));
$javascript = drupal_get_js();
$expected = implode("\n", array(
'',
'',
'',
'',
+ "\n\n",
+ "\n",
+ "\n",
+ "",
+ '',
+ "",
));
- $this->assertTrue(strpos($javascript, $expected) > 0, t('Unaggregated JavaScript is added in the expected group order.'));
+ $this->assertTrue(strpos($javascript, $expected) !== FALSE, t('Unaggregated JavaScript is added in the expected group order.'));
- // Now ensure that with aggregation on, one file is made for the
- // 'every_page' files, and one file is made for the others.
- drupal_static_reset('drupal_add_js');
+ // Enable aggregation.
variable_set('preprocess_js', 1);
+
+ // Ensure a file is made for the 'every_page' files, one for different
+ // browser conditions and another for the rest.
+ drupal_static_reset('drupal_add_js');
+
+ // drupal_add_js() adds jquery.js, jquery.once.js and drupal.js if it's
+ // array is empty. This affects expect aggregation values, inject a fake
+ // settings array so they don't get added.
+ $settings = drupal_js_defaults(array());
+ $settings['type'] = 'setting';
+ $settings['group'] = JS_LIBRARY;
+ $drupal_add_js = &drupal_static('drupal_add_js', array());
+ $drupal_add_js['settings'] = $settings;
+
+ // Duplicate order and options above, but check for aggregated values.
drupal_add_js('misc/ajax.js');
drupal_add_js('misc/authorize.js', array('every_page' => TRUE));
drupal_add_js('misc/autocomplete.js');
drupal_add_js('misc/batch.js', array('every_page' => TRUE));
+ drupal_add_js('misc/collapse.js', array('browsers' => array(
+ 'IE' => 'lt IE 8',
+ '!IE' => FALSE,
+ )));
+ drupal_add_js('misc/states.js', array('browsers' => array(
+ 'IE' => 'lte IE 6',
+ '!IE' => FALSE,
+ )));
+ drupal_add_js('misc/textarea.js', array('browsers' => array(
+ 'IE' => 'gt IE 7',
+ '!IE' => TRUE,
+ )));
+ drupal_add_js('misc/progress.js', array('browsers' => array(
+ 'IE' => 'lt IE 8',
+ '!IE' => FALSE,
+ )));
$js_items = drupal_add_js();
$javascript = drupal_get_js();
$expected = implode("\n", array(
'',
'',
+ "\n\n",
+ "\n",
+ "",
+ '',
+ "",
));
- $this->assertTrue(strpos($javascript, $expected) > 0, t('JavaScript is aggregated in the expected groups and order.'));
+ $this->assertTrue(strpos($javascript, $expected) !== FALSE, t('JavaScript is aggregated in the expected groups and order.'));
}
/**
diff --git a/modules/system/system.module b/modules/system/system.module
index bfeeb50..d4f3bc4 100644
--- a/modules/system/system.module
+++ b/modules/system/system.module
@@ -322,12 +322,6 @@ function system_element_info() {
'#group_callback' => 'drupal_group_css',
'#aggregate_callback' => 'drupal_aggregate_css',
);
- $types['scripts'] = array(
- '#items' => array(),
- '#pre_render' => array('drupal_pre_render_scripts'),
- '#group_callback' => 'drupal_group_js',
- '#aggregate_callback' => 'drupal_aggregate_js',
- );
// Input elements.
$types['submit'] = array(