diff --git a/includes/common.inc b/includes/common.inc index da8996a1b9..0ad15cf86b 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -4418,8 +4418,10 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS // Filter out elements of the given scope. $items = array(); foreach ($javascript as $key => $item) { - if ($item['scope'] == $scope) { - $items[$key] = $item; + if (array_key_exists('scope', $item)) { + if ($item['scope'] == $scope) { + $items[$key] = $item; + } } } @@ -4477,14 +4479,32 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS ), ); foreach ($items as $item) { - $query_string = empty($item['version']) ? $default_query_string : $js_version_string . $item['version']; + $query_string = empty($item['version']) ? $default_query_string : $js_version_string . $item['version']; switch ($item['type']) { case 'setting': $js_element = $element; - $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; + // The "drupal_settings_json" variable determines if drupalSettings + // should be displayed in JSON format, to allow for CSP (content + // security policy). This should be set to FALSE by default to allow for + // backwards compatibility. + if (variable_get('drupal_settings_json', FALSE)) { + $js_element['#attributes'] = array( + // This type attribute prevents this from being parsed as an inline + // script. + 'type' => 'application/json', + 'data-drupal-selector' => 'drupal-settings-json', + ); + $js_element['#value'] = drupal_json_encode(drupal_array_merge_deep_array($item['data'])); + $output .= theme('html_tag', array('element' => $js_element)); + $js_element = $element; + $js_element['#attributes']['src'] = file_create_url('misc/drupal-settings-loader.js') . $query_string_separator . REQUEST_TIME; + } + else { + $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 .= theme('html_tag', array('element' => $js_element)); break; diff --git a/misc/drupal-settings-loader.js b/misc/drupal-settings-loader.js new file mode 100644 index 0000000000..dc6f343883 --- /dev/null +++ b/misc/drupal-settings-loader.js @@ -0,0 +1,15 @@ +/** + * @file + * Parse inline JSON and initialize the drupal.settings global object. + */ + +(function ($) { + use strict; + + var settingsElement = document.querySelector('script[type="application/json"][data-drupal-selector="drupal-settings-json"]'); + + if (settingsElement !== null) { + DrupalSettings = JSON.parse(settingsElement.textContent); + jQuery.extend(Drupal.settings, DrupalSettings); + } +})(jQuery); diff --git a/modules/locale/locale.module b/modules/locale/locale.module index 768fead676..7f20d631e2 100644 --- a/modules/locale/locale.module +++ b/modules/locale/locale.module @@ -908,15 +908,17 @@ function locale_js_alter(&$javascript) { require_once DRUPAL_ROOT . '/includes/locale.inc'; foreach ($javascript as $item) { - if ($item['type'] == 'file') { - $files = TRUE; - $filepath = $item['data']; - if (!in_array($filepath, $parsed)) { - // Don't parse our own translations files. - if (substr($filepath, 0, strlen($dir)) != $dir) { - _locale_parse_js_file($filepath); - $parsed[] = $filepath; - $new_files = TRUE; + if (array_key_exists('type', $item)) { + if ($item['type'] == 'file') { + $files = TRUE; + $filepath = $item['data']; + if (!in_array($filepath, $parsed)) { + // Don't parse our own translations files. + if (substr($filepath, 0, strlen($dir)) != $dir) { + _locale_parse_js_file($filepath); + $parsed[] = $filepath; + $new_files = TRUE; + } } } } diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index 51407991dd..ac59a24e71 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -2934,8 +2934,16 @@ protected function drupalSetContent($content, $url = 'internal:') { $this->plainTextContent = FALSE; $this->elements = FALSE; $this->drupalSettings = array(); - if (preg_match('/jQuery\.extend\(Drupal\.settings, (.*?)\);/', $content, $matches)) { - $this->drupalSettings = drupal_json_decode($matches[1]); + + if (variable_get('drupal_settings_json')) { + if (preg_match('/\{(?:[^{}]|(?R))*\}/x', $content, $matches)) { + $this->drupalSettings = drupal_json_decode($matches[0]); + } + } + else { + if (preg_match('/jQuery\.extend\(Drupal\.settings, (.*?)\);/', $content, $matches)) { + $this->drupalSettings = drupal_json_decode($matches[1]); + } } } diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test index 0f991c3c74..3b6d1d26c8 100644 --- a/modules/simpletest/tests/common.test +++ b/modules/simpletest/tests/common.test @@ -1545,6 +1545,11 @@ function testJavaScriptAlwaysUseJQuery() { $this->assertRaw('misc/drupal.js', 'Default behavior: The front page of the site includes drupal.js.'); $this->assertRaw('Drupal.settings', 'Default behavior: The front page of the site includes Drupal settings.'); $this->assertRaw('basePath', 'Default behavior: The front page of the site includes the basePath Drupal setting.'); + variable_set('drupal_settings_json', TRUE); + $this->drupalGet(''); + $this->assertRaw('drupal-settings-json', 'When "drupal_settings_json" is TRUE. Default behavior: The front page of the site includes Drupal settings in json format.'); + $this->assertRaw('misc/drupal-settings-loader.js', 'When "drupal_settings_json" is TRUE. Default behavior: The front page of the site includes Drupal settings loader.'); + variable_del('drupal_settings_json'); // The default front page should not use jQuery and other standard scripts // and settings when the 'javascript_always_use_jquery' variable is set to @@ -1553,8 +1558,15 @@ function testJavaScriptAlwaysUseJQuery() { $this->drupalGet(''); $this->assertNoRaw('misc/jquery.js', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include jquery.js.'); $this->assertNoRaw('misc/drupal.js', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include drupal.js.'); + $this->assertNoRaw('drupal-settings-json', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include Drupal settings in json format.'); + $this->assertNoRaw('misc/drupal-settings-loader.js', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include Drupal settings loader.'); $this->assertNoRaw('Drupal.settings', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include Drupal settings.'); $this->assertNoRaw('basePath', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include the basePath Drupal setting.'); + variable_set('drupal_settings_json', TRUE); + $this->drupalGet(''); + $this->assertNoRaw('drupal-settings-json', 'When "javascript_always_use_jquery" is FALSE and "drupal_settings_json" is TRUE: The front page of the site does not include Drupal settings in json format.'); + $this->assertNoRaw('misc/drupal-settings-loader.js', 'When "javascript_always_use_jquery" is FALSE and "drupal_settings_json" is TRUE: The front page of the site does not include Drupal settings loader.'); + variable_del('drupal_settings_json'); variable_del('javascript_always_use_jquery'); // When only settings have been added via drupal_add_js(), drupal_get_js() @@ -1567,6 +1579,12 @@ function testJavaScriptAlwaysUseJQuery() { $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes Drupal.settings.'); $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes the basePath Drupal setting.'); $this->assertTrue(strpos($javascript, 'testJavaScriptSetting') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes the added Drupal settings.'); + variable_set('drupal_settings_json', TRUE); + $javascript = drupal_get_js(); + $this->assertTrue(strpos($javascript, 'Drupal.settings') === FALSE); + $this->assertTrue(strpos($javascript, 'drupal-settings-json') !== FALSE, 'When "drupal_settings_json" is TRUE. Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes Drupal.settings in json format.'); + $this->assertTrue(strpos($javascript, 'misc/drupal-settings-loader.js') !== FALSE, '"drupal_settings_json" is TRUE. Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes drupal-settings-loader.js.'); + variable_del('drupal_settings_json'); // When only settings have been added via drupal_add_js() and the // 'javascript_always_use_jquery' variable is set to FALSE, drupal_get_js() @@ -1582,6 +1600,12 @@ function testJavaScriptAlwaysUseJQuery() { $this->assertTrue(strpos($javascript, 'Drupal.settings') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include Drupal.settings.'); $this->assertTrue(strpos($javascript, 'basePath') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include the basePath Drupal setting.'); $this->assertTrue(strpos($javascript, 'testJavaScriptSetting') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include the added Drupal settings.'); + variable_set('drupal_settings_json', TRUE); + $javascript = drupal_get_js(); + $this->assertTrue(strpos($javascript, 'drupal-settings-json') === FALSE, 'When "javascript_always_use_jquery" is FALSE and "drupal_settings_json" is TRUE: The JavaScript returned by drupal_get_js() when only settings have been added does not include Drupal.settings in json format.'); + $this->assertTrue(strpos($javascript, 'misc/drupal-settings-loader.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE and "drupal_settings_json" is TRUE: The JavaScript returned by drupal_get_js() when only settings have been added does not include drupal-settings-loader.js.'); + $this->assertTrue(strpos($javascript, 'Drupal.settings') === FALSE, 'When "javascript_always_use_jquery" is FALSE and "drupal_settings_json" is TRUE: The JavaScript returned by drupal_get_js() when only settings have been added does not include Drupal.settings.'); + variable_del('drupal_settings_json'); variable_del('javascript_always_use_jquery'); // When a regular file has been added via drupal_add_js(), drupal_get_js() @@ -1594,6 +1618,12 @@ function testJavaScriptAlwaysUseJQuery() { $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes Drupal.settings.'); $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the basePath Drupal setting.'); $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the custom file.'); + variable_set('drupal_settings_json', TRUE); + $javascript = drupal_get_js(); + $this->assertTrue(strpos($javascript, 'Drupal.settings') === FALSE, 'When "drupal_settings_json" is TRUE. The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added does not includes Drupal.settings.'); + $this->assertTrue(strpos($javascript, 'drupal-settings-json') !== FALSE, 'When "drupal_settings_json" is TRUE. The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes Drupal.settings in json format.'); + $this->assertTrue(strpos($javascript, 'misc/drupal-settings-loader.js') !== FALSE, ' When "drupal_settings_json" is TRUE. The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes drupal-settings-loader.js.'); + variable_del('drupal_settings_json'); // When a regular file has been added via drupal_add_js() and the // 'javascript_always_use_jquery' variable is set to FALSE, drupal_get_js() @@ -1608,6 +1638,11 @@ function testJavaScriptAlwaysUseJQuery() { $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes Drupal.settings.'); $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the basePath Drupal setting.'); $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the custom file.'); + variable_set('drupal_settings_json', TRUE); + $javascript = drupal_get_js(); + $this->assertTrue(strpos($javascript, 'drupal-settings-json') !== FALSE, 'When "javascript_always_use_jquery" is FALSE and "drupal_settings_json" is TRUE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes Drupal.settings in json format.'); + $this->assertTrue(strpos($javascript, 'misc/drupal-settings-loader.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE and "drupal_settings_json" is TRUE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes drupal-settings-loader.js.'); + variable_del('drupal_settings_json'); variable_del('javascript_always_use_jquery'); // When a file that does not require jQuery has been added via @@ -1621,6 +1656,11 @@ function testJavaScriptAlwaysUseJQuery() { $this->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes Drupal.settings.'); $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes the basePath Drupal setting.'); $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes the custom file.'); + variable_set('drupal_settings_json', TRUE); + $javascript = drupal_get_js(); + $this->assertTrue(strpos($javascript, 'drupal-settings-json') !== FALSE, 'When "drupal_settings_json" is TRUE. The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes Drupal.settings in json format.'); + $this->assertTrue(strpos($javascript, 'misc/drupal-settings-loader.js') !== FALSE, 'When "drupal_settings_json" is TRUE. The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes drupal-settings-loader.js.'); + variable_del('drupal_settings_json'); // When a file that does not require jQuery has been added via // drupal_add_js() and the 'javascript_always_use_jquery' variable is set @@ -1635,7 +1675,12 @@ function testJavaScriptAlwaysUseJQuery() { $this->assertTrue(strpos($javascript, 'Drupal.settings') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include Drupal.settings.'); $this->assertTrue(strpos($javascript, 'basePath') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include the basePath Drupal setting.'); $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes the custom file.'); + variable_set('drupal_settings_json', TRUE); + $javascript = drupal_get_js(); + $this->assertTrue(strpos($javascript, 'drupal-settings-json') === FALSE, 'When "javascript_always_use_jquery" is FALSE and "drupal_settings_json" is TRUE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include Drupal.settings in json format.'); + $this->assertTrue(strpos($javascript, 'misc/drupal-settings-loader.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE and "drupal_settings_json" is TRUE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not includes the drupal-settings-loader.js.'); variable_del('javascript_always_use_jquery'); + variable_del('drupal_settings_json'); // When 'javascript_always_use_jquery' is set to FALSE and a file that does // not require jQuery is added, followed by one that does, drupal_get_js() @@ -1652,7 +1697,12 @@ function testJavaScriptAlwaysUseJQuery() { $this->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes the basePath Drupal setting.'); $this->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes the first custom file.'); $this->assertTrue(strpos($javascript, 'misc/ajax.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes the second custom file.'); + variable_set('drupal_settings_json', TRUE); + $javascript = drupal_get_js(); + $this->assertTrue(strpos($javascript, 'drupal-settings-json') !== FALSE, 'When "javascript_always_use_jquery" is FALSE and "drupal_settings_json" is TRUE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes Drupal.settings in json format.'); + $this->assertTrue(strpos($javascript, 'misc/drupal-settings-loader.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE and "drupal_settings_json" is TRUE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes drupal-settings-loader.js.'); variable_del('javascript_always_use_jquery'); + variable_del('drupal_settings_json'); } /**