diff --git a/core/includes/common.inc b/core/includes/common.inc index f3cdd69..e383edd 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -4043,77 +4043,43 @@ function drupal_region_class($region) { * should load the JavaScript item. See * drupal_pre_render_conditional_comments() for details. * - * @return + * @return array * The current array of JavaScript files, settings, and in-line code, - * including Drupal defaults, anything previously added with calls to - * drupal_add_js(), and this function call's additions. + * anything previously added with calls to drupal_add_js(), this function + * call's additions, and Drupal's default settings. The default jQuery and + * Drupal libraries are only conditionally added later in + * drupal_add_js_page_defaults(). * * @see drupal_get_js() + * @see ajax_render() + * @see drupal_add_js_page_defaults() */ -function drupal_add_js($data = NULL, $options = NULL) { - $javascript = &drupal_static(__FUNCTION__, array()); +function &drupal_add_js($data = NULL, $options = NULL) { + $javascript = &drupal_static(__FUNCTION__); - // Construct the options, taking the defaults into consideration. - if (isset($options)) { - if (!is_array($options)) { - $options = array('type' => $options); - } - } - else { - $options = array(); + // Initialize default settings on first call. + if (!isset($javascript)) { + $javascript = drupal_js_settings_default(); } - $options += drupal_js_defaults($data); - - // Preprocess can only be set if caching is enabled. - $options['preprocess'] = $options['cache'] ? $options['preprocess'] : FALSE; - - // Tweak the weight so that files of the same weight are included in the - // order of the calls to drupal_add_js(). - $options['weight'] += count($javascript) / 1000; 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)) { - // url() generates the script and prefix using hook_url_outbound_alter(). - // Instead of running the hook_url_outbound_alter() again here, extract - // them from url(). - // @todo Make this less hacky: http://drupal.org/node/1547376. - $scriptPath = $GLOBALS['script_path']; - $pathPrefix = ''; - url('', array('script' => &$scriptPath, 'prefix' => &$pathPrefix)); - $javascript = array( - 'settings' => array( - 'data' => array( - array('basePath' => base_path()), - array('scriptPath' => $scriptPath), - array('pathPrefix' => $pathPrefix), - ), - 'type' => 'setting', - 'scope' => 'header', - 'group' => JS_SETTING, - 'every_page' => TRUE, - 'weight' => 0, - 'browsers' => array(), - ), - 'core/misc/drupal.js' => array( - 'data' => 'core/misc/drupal.js', - 'type' => 'file', - 'scope' => 'header', - 'group' => JS_LIBRARY, - 'every_page' => TRUE, - 'weight' => -1, - 'preprocess' => TRUE, - 'cache' => TRUE, - 'defer' => FALSE, - 'browsers' => array(), - ), - ); - // Register all required libraries. - drupal_add_library('system', 'jquery', TRUE); - drupal_add_library('system', 'jquery.once', TRUE); - drupal_add_library('system', 'html5shiv', TRUE); + // Construct the options, taking the defaults into consideration. + if (isset($options)) { + if (!is_array($options)) { + $options = array('type' => $options); + } + } + else { + $options = array(); } + $options += drupal_js_defaults($data); + + // Preprocess can only be set if caching is enabled. + $options['preprocess'] = $options['cache'] ? $options['preprocess'] : FALSE; + + // Tweak the weight so that files of the same weight are included in the + // order of the calls to drupal_add_js(). + $options['weight'] += count($javascript) / 1000; switch ($options['type']) { case 'setting': @@ -4132,10 +4098,76 @@ function drupal_add_js($data = NULL, $options = NULL) { $javascript[$options['data']] = $options; } } + + return $javascript; +} + +/** + * The default JavaScript settings for Drupal. + * + * @see drupal_add_js() + */ +function drupal_js_settings_default() { + $javascript = array(); + + // url() generates the script and prefix using hook_url_outbound_alter(). + // Instead of running the hook_url_outbound_alter() again here, extract + // them from url(). + // @todo Make this less hacky: http://drupal.org/node/1547376. + $scriptPath = $GLOBALS['script_path']; + $pathPrefix = ''; + url('', array('script' => &$scriptPath, 'prefix' => &$pathPrefix)); + + $javascript['settings'] = array( + 'data' => array( + array('basePath' => base_path()), + array('scriptPath' => $scriptPath), + array('pathPrefix' => $pathPrefix), + ), + 'type' => 'setting', + 'scope' => 'header', + 'group' => JS_SETTING, + 'every_page' => TRUE, + 'weight' => 0, + 'browsers' => array(), + ); + return $javascript; } /** + * Conditionally adds the default Drupal/jQuery libraries to the page. + * + * @see drupal_add_js() + * @see drupal_get_js() + * @see template_process_html() + */ +function drupal_add_js_page_defaults() { + $javascript = &drupal_add_js(); + + // If any JavaScript (except settings) has been added, include the required + // base libraries. + if (array_diff_key($javascript, array('settings' => 0))) { + drupal_add_library('system', 'jquery', TRUE); + drupal_add_library('system', 'jquery.once', TRUE); + + drupal_add_js('core/misc/drupal.js', array( + 'group' => JS_LIBRARY, + 'every_page' => TRUE, + 'weight' => -1, + )); + } + // Otherwise, remove all settings. + else { + $javascript = array(); + } + + // Unconditionally add the html5shiv to all pages; it is required for + // backwards-compatible CSS styling and has no dependencies. + drupal_add_library('system', 'html5shiv', TRUE); +} + +/** * Constructs an array of the defaults that are used for JavaScript items. * * @param $data @@ -4186,7 +4218,7 @@ function drupal_js_defaults($data = NULL) { * $javascript, useful when the calling function passes a $javascript array * that has already been altered. * - * @return + * @return string * All JavaScript code segments and includes for the scope as HTML tags. * * @see drupal_add_js() @@ -4201,36 +4233,36 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS return ''; } - // Allow modules to alter the JavaScript. - if (!$skip_alter) { - drupal_alter('js', $javascript); - } - // Filter out elements of the given scope. $items = array(); foreach ($javascript as $key => $item) { - if ($item['scope'] == $scope) { + if (isset($item['scope']) && $item['scope'] == $scope) { $items[$key] = $item; } } + // Allow modules to alter the JavaScript for the given scope. + if (!$skip_alter) { + drupal_alter('js', $items); + } + // 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 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; + // If there is no 'settings' key or no actual JavaScript has been added + // to the page, then ajaxPageState settings are not needed either. + // @see drupal_add_js_page_defaults() + if (isset($items['settings']) && ($files = array_diff_key($items, array('settings' => 0)))) { + $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($files), 1); + drupal_add_js($setting, 'setting'); + + // If the 'header' scope is output, then this is the final call to + // drupal_get_js() within page rendering, so directly inject the setting. + if ($scope == 'header') { + $items['settings']['data'][] = $setting; + } } // Render the HTML needed to load the JavaScript. diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 2a88969..72430c0 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -2663,7 +2663,10 @@ function template_process_html(&$variables) { $variables['page_top'] = drupal_render($variables['page']['page_top']); $variables['page_bottom'] = drupal_render($variables['page']['page_bottom']); // Place the rendered HTML for the page body into a top level variable. - $variables['page'] = $variables['page']['#children']; + $variables['page'] = $variables['page']['#children']; + + // Conditionally add the default Drupal/jQuery libraries to the page. + drupal_add_js_page_defaults(); $variables['page_bottom'] .= drupal_get_js('footer'); $variables['head'] = drupal_get_html_head(); diff --git a/core/modules/system/tests/common.test b/core/modules/system/tests/common.test index 46b379e..ce891a7 100644 --- a/core/modules/system/tests/common.test +++ b/core/modules/system/tests/common.test @@ -1142,14 +1142,16 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { * Test default JavaScript is empty. */ function testDefault() { - $this->assertEqual(array(), drupal_add_js(), t('Default JavaScript is empty.')); + $this->assertEqual(drupal_js_settings_default(), drupal_add_js(), t('Default JavaScript is empty.')); } /** * Test adding a JavaScript file. */ function testAddFile() { - $javascript = drupal_add_js('core/misc/collapse.js'); + drupal_add_js('core/misc/collapse.js'); + drupal_add_js_page_defaults(); + $javascript = drupal_add_js(); $this->assertTrue(array_key_exists('core/misc/jquery.js', $javascript), t('jQuery is added when a file is added.')); $this->assertTrue(array_key_exists('core/misc/drupal.js', $javascript), t('Drupal.js is added when file is added.')); $this->assertTrue(array_key_exists('core/misc/html5.js', $javascript), t('html5.js is added when file is added.')); @@ -1191,6 +1193,7 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { // Only the second of these two entries should appear in Drupal.settings. drupal_add_js(array('commonTestArray' => array('key' => 'commonTestOldValue')), 'setting'); drupal_add_js(array('commonTestArray' => array('key' => 'commonTestNewValue')), 'setting'); + drupal_add_js('core/misc/jquery.js'); $javascript = drupal_get_js('header'); $this->assertTrue(strpos($javascript, 'basePath') > 0, t('Rendered JavaScript header returns basePath setting.')); @@ -1219,7 +1222,7 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { function testReset() { drupal_add_js('core/misc/collapse.js'); drupal_static_reset('drupal_add_js'); - $this->assertEqual(array(), drupal_add_js(), t('Resetting the JavaScript correctly empties the cache.')); + $this->assertEqual(drupal_js_settings_default(), drupal_add_js(), t('Resetting the JavaScript correctly empties the cache.')); } /** @@ -1227,10 +1230,11 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { */ function testAddInline() { $inline = 'jQuery(function () { });'; - $javascript = drupal_add_js($inline, array('type' => 'inline', 'scope' => 'footer')); + drupal_add_js($inline, array('type' => 'inline', 'scope' => 'footer')); + drupal_add_js_page_defaults(); + $javascript = drupal_add_js(); $this->assertTrue(array_key_exists('core/misc/jquery.js', $javascript), t('jQuery is added when inline scripts are added.')); - $data = end($javascript); - $this->assertEqual($inline, $data['data'], t('Inline JavaScript is correctly added to the footer.')); + $this->assertEqual($inline, $javascript[0]['data'], t('Inline JavaScript is correctly added.')); } /** @@ -1274,8 +1278,12 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { * Test adding a JavaScript file with a different weight. */ function testDifferentWeight() { - $javascript = drupal_add_js('core/misc/collapse.js', array('weight' => 2)); - $this->assertEqual($javascript['core/misc/collapse.js']['weight'], 2, t('Adding a JavaScript file with a different weight caches the given weight.')); + drupal_add_js('core/misc/collapse.js', array('weight' => 2)); + drupal_add_js('core/misc/tabledrag.js', array('weight' => 999)); + drupal_add_js_page_defaults(); + $javascript = drupal_add_js(); + $this->assertEqual($javascript['core/misc/collapse.js']['weight'], 2.001, t('Adding a JavaScript file with a different weight caches the given weight.')); + $this->assertEqual($javascript['core/misc/tabledrag.js']['weight'], 999.002, t('Adding a JavaScript file with a different weight caches the given weight.')); } /** @@ -1321,6 +1329,7 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { drupal_add_js('core/misc/authorize.js', array('every_page' => TRUE)); drupal_add_js('core/misc/autocomplete.js'); drupal_add_js('core/misc/batch.js', array('every_page' => TRUE)); + drupal_add_js_page_defaults(); $javascript = drupal_get_js(); $expected = implode("\n", array( '', @@ -1340,6 +1349,7 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { drupal_add_js('core/misc/authorize.js', array('every_page' => TRUE)); drupal_add_js('core/misc/autocomplete.js'); drupal_add_js('core/misc/batch.js', array('every_page' => TRUE)); + drupal_add_js_page_defaults(); $js_items = drupal_add_js(); $javascript = drupal_get_js(); $expected = implode("\n", array( @@ -1441,6 +1451,7 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { // flag, then by weight (see drupal_sort_css_js()), so to test the effect of // weight, we need the other two options to be the same. drupal_add_js('core/misc/collapse.js', array('group' => JS_LIBRARY, 'every_page' => TRUE, 'weight' => -21)); + drupal_add_js_page_defaults(); $javascript = drupal_get_js(); $this->assertTrue(strpos($javascript, 'core/misc/collapse.js') < strpos($javascript, 'core/misc/jquery.js'), t('Rendering a JavaScript file above jQuery.')); } @@ -1556,6 +1567,68 @@ class CommonJavaScriptTestCase extends DrupalWebTestCase { } /** + * Tests addition of jQuery and Drupal default libraries and settings. + */ +class CommonJavaScriptDefaultsTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'JavaScript defaults', + 'description' => 'Tests addition of jQuery and Drupal default libraries and settings.', + 'group' => 'Common', + ); + } + + /** + * Tests JavaScript default settings and libraries. + * + * Note: Where this test says "no JavaScript", it means no Drupal and jQuery + * scripts. The html5shiv is expected to appear always. + */ + function testJavaScriptDefaults() { + // A regular page should not contain any JavaScript by default. + $this->drupalGet(''); + $this->assertNoRaw('Drupal.settings', 'Drupal.settings not found.'); + $this->assertNoRaw('jquery', 'jQuery not found.'); + $this->assertRaw('html5.js', 'html5shiv found.'); + + // Repeat, with test helper. + drupal_static_reset(); + $this->drupalRenderPage(); + $this->assertNoRaw('Drupal.settings', 'Drupal.settings not found.'); + $this->assertNoRaw('jquery', 'jQuery not found.'); + $this->assertRaw('html5.js', 'html5shiv found.'); + + // When only settings are added in a request, no JavaScript should appear. + drupal_static_reset(); + drupal_add_js(array('foo' => 'bar'), 'setting'); + $this->drupalRenderPage(); + $this->assertNoRaw('Drupal.settings', 'Drupal.settings not found.'); + $this->assertNoRaw('jquery', 'jQuery not found.'); + $this->assertRaw('html5.js', 'html5shiv found.'); + + // When adding any other JavaScript, default libraries and settings should + // appear. + drupal_static_reset(); + drupal_add_js('core/misc/ajax.js'); + $this->drupalRenderPage(); + $this->assertRaw('Drupal.settings', 'Drupal.settings found.'); + $this->assertRaw('jquery', 'jQuery found.'); + $this->assertRaw('html5.js', 'html5shiv found.'); + } + + /** + * Sets the rendered JavaScript as browser content and logs it verbosely. + */ + function drupalRenderPage() { + // drupal_get_js() is called for each region/scope during page rendering. + // @see template_process_html() + $page = drupal_render_page(''); + $this->drupalSetContent($page); + $this->verbose($this->drupalGetContent()); + } +} + +/** * Tests for drupal_render(). */ class CommonDrupalRenderTestCase extends DrupalWebTestCase {