Commit 77c613e3 authored by catch's avatar catch

Issue #2382533 by Wim Leers: Attach assets only via the asset library system

parent cd0cb0d7
This diff is collapsed.
......@@ -158,21 +158,6 @@ public function render(array $css_assets) {
}
break;
// Output a STYLE tag for an inline CSS asset. The asset's 'data'
// property contains the CSS content.
case 'inline':
$element = $style_element_defaults;
$element['#value'] = $css_asset['data'];
$element['#attributes']['media'] = $css_asset['media'];
$element['#browsers'] = $css_asset['browsers'];
// For inline CSS to validate as XHTML, all CSS containing XHTML needs
// to be wrapped in CDATA. To make that backwards compatible with HTML
// 4, we need to comment out the CDATA-tag.
$element['#value_prefix'] = "\n/* <![CDATA[ */\n";
$element['#value_suffix'] = "\n/* ]]> */\n";
$elements[] = $element;
break;
// Output a LINK tag for an external CSS asset. The asset's 'data'
// property contains the full URL.
case 'external':
......
......@@ -72,14 +72,8 @@ public function render(array $js_assets) {
$element['#value_suffix'] = $embed_suffix;
break;
case 'inline':
$element['#value_prefix'] = $embed_prefix;
$element['#value'] = $js_asset['data'];
$element['#value_suffix'] = $embed_suffix;
break;
case 'file':
$query_string = empty($js_asset['version']) ? $default_query_string : 'v=' . $js_asset['version'];
$query_string = $js_asset['version'] == -1 ? $default_query_string : 'v=' . $js_asset['version'];
$query_string_separator = (strpos($js_asset['data'], '?') !== FALSE) ? '&' : '?';
$element['#attributes']['src'] = file_create_url($js_asset['data']);
// Only add the cache-busting query string if this isn't an aggregate
......
......@@ -197,12 +197,6 @@ public function buildByExtension($extension) {
$library[$type][] = $options;
}
}
// @todo Convert all uses of #attached[library][]=array('provider','name')
// into #attached[library][]='provider/name' and remove this.
foreach ($library['dependencies'] as $i => $dependency) {
$library['dependencies'][$i] = $dependency;
}
}
return $libraries;
......
......@@ -30,7 +30,7 @@ drupal.comment-new-indicator:
- core/jquery
- core/jquery.once
- core/drupal
- history/drupal.history
- history/api
- core/drupal.displace
drupal.node-new-comments-link:
......@@ -41,4 +41,4 @@ drupal.node-new-comments-link:
- core/jquery
- core/jquery.once
- core/drupal
- history/drupal.history
- history/api
drupal.history:
api:
version: VERSION
js:
js/history.js: {}
......@@ -7,3 +7,10 @@ drupal.history:
- core/drupalSettings
- core/drupal
- core/drupal.ajax
mark-as-read:
version: VERSION
js:
js/mark-as-read.js: { scope: footer }
dependencies:
- history/api
......@@ -138,11 +138,8 @@ function history_node_view_alter(array &$build, EntityInterface $node, EntityVie
// When the window's "load" event is triggered, mark the node as read.
// This still allows for Drupal behaviors (which are triggered on the
// "DOMContentReady" event) to add "new" and "updated" indicators.
$build['#attached']['js'][] = array(
'data' => 'window.addEventListener("load",function(){Drupal.history.markAsRead(' . $node->id() . ');},false);',
'type' => 'inline',
);
$build['#attached']['library'][] = 'history/drupal.history';
$build['#attached']['library'][] = 'history/mark-as-read';
$build['#attached']['drupalSettings']['history']['nodesToMarkAsRead'][$node->id()] = TRUE;
}
}
......
/**
* Marks the nodes listed in drupalSettings.history.nodesToMarkAsRead as read.
*
* Uses the History module JavaScript API.
*/
(function (window, Drupal, drupalSettings) {
"use strict";
// When the window's "load" event is triggered, mark all enumerated nodes as
// read. This still allows for Drupal behaviors (which are triggered on the
// "DOMContentReady" event) to add "new" and "updated" indicators.
window.addEventListener('load', function() {
if (drupalSettings.history && drupalSettings.history.nodesToMarkAsRead) {
Object.keys(drupalSettings.history.nodesToMarkAsRead).forEach(Drupal.history.markAsRead);
}
});
})(window, Drupal, drupalSettings);
......@@ -119,8 +119,9 @@ function testHistory() {
$this->drupalGet('node/' . $nid);
// JavaScript present to record the node read.
$settings = $this->getDrupalSettings();
$this->assertTrue(isset($settings['ajaxPageState']['js']['core/modules/history/js/history.js']), 'drupal.history library is present.');
$this->assertRaw('Drupal.history.markAsRead(' . $nid . ')', 'History module JavaScript API call to mark node as read present on page.');
$this->assertTrue(isset($settings['ajaxPageState']['js']['core/modules/history/js/history.js']), 'history/api library is present.');
$this->assertTrue(isset($settings['ajaxPageState']['js']['core/modules/history/js/mark-as-read.js']), 'history/mark-as-read library is present.');
$this->assertEqual([$nid => TRUE], $settings['history']['nodesToMarkAsRead'], 'drupalSettings to mark node as read are present.');
// Simulate JavaScript: perform HTTP request to mark node as read.
$response = $this->markNodeAsRead($nid);
......
......@@ -99,7 +99,8 @@ function quickedit_library_alter(array &$library, $name, $theme = NULL) {
}
if (isset($info['quickedit_stylesheets']) && is_array($info['quickedit_stylesheets'])) {
foreach ($info['quickedit_stylesheets'] as $path) {
$library['css'][$theme_path . '/' . $path] = array(
$library['css'][] = array(
'data' => $theme_path . '/' . $path,
'group' => CSS_AGGREGATE_THEME,
'weight' => CSS_THEME,
);
......
......@@ -35,44 +35,18 @@ public function testAJAXRender() {
* Tests AjaxResponse::prepare() AJAX commands ordering.
*/
public function testOrder() {
$path = drupal_get_path('module', 'system');
$expected_commands = array();
// Expected commands, in a very specific order.
$expected_commands[0] = new SettingsCommand(array('ajax' => 'test'), TRUE);
drupal_static_reset('_drupal_add_css');
$attached = array(
'#attached' => array(
'css' => array(
$path . '/css/system.admin.css' => array(),
$path . '/css/system.maintenance.css' => array()
),
),
);
drupal_render($attached);
drupal_process_attached($attached);
$build['#attached']['library'][] = 'ajax_test/order-css-command';
drupal_process_attached($build);
$expected_commands[1] = new AddCssCommand(drupal_get_css(_drupal_add_css(), TRUE));
drupal_static_reset('_drupal_add_js');
$attached = array(
'#attached' => array(
'js' => array(
$path . '/system.js' => array(),
),
),
);
drupal_render($attached);
drupal_process_attached($attached);
$build['#attached']['library'][] = 'ajax_test/order-js-command';
drupal_process_attached($build);
$expected_commands[2] = new PrependCommand('head', drupal_get_js('header', _drupal_add_js(), TRUE));
drupal_static_reset('_drupal_add_js');
$attached = array(
'#attached' => array(
'js' => array(
$path . '/system.modules.js' => array('scope' => 'footer'),
),
),
);
drupal_render($attached);
drupal_process_attached($attached);
$expected_commands[3] = new AppendCommand('body', drupal_get_js('footer', _drupal_add_js(), TRUE));
$expected_commands[4] = new HtmlCommand('body', 'Hello, world!');
......
This diff is collapsed.
<?php
/**
* @file
* Definition of Drupal\system\Tests\Common\CascadingStylesheetsTest.
*/
namespace Drupal\system\Tests\Common;
use Drupal\Component\Utility\String;
use Drupal\simpletest\KernelTestBase;
/**
* Tests adding various cascading stylesheets to the page.
*
* @group Common
*/
class CascadingStylesheetsTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'system');
protected function setUp() {
parent::setUp();
// Reset _drupal_add_css() before each test.
drupal_static_reset('_drupal_add_css');
}
/**
* Checks that default stylesheets are empty.
*/
function testDefault() {
$this->assertEqual(array(), _drupal_add_css(), 'Default CSS is empty.');
}
/**
* Tests adding a file stylesheet.
*/
function testAddFile() {
$path = drupal_get_path('module', 'simpletest') . '/css/simpletest.module.css';
$css = _drupal_add_css($path);
$this->assertEqual($css['simpletest.module.css']['data'], $path);
}
/**
* Tests adding an external stylesheet.
*/
function testAddExternal() {
$path = 'http://example.com/style.css';
$css = _drupal_add_css($path, 'external');
$this->assertEqual($css[$path]['type'], 'external', 'Adding an external CSS file caches it properly.');
}
/**
* Makes sure that resetting the CSS empties the cache.
*/
function testReset() {
drupal_static_reset('_drupal_add_css');
$this->assertEqual(array(), _drupal_add_css(), 'Resetting the CSS empties the cache.');
}
/**
* Tests rendering the stylesheets.
*/
function testRenderFile() {
$css = drupal_get_path('module', 'simpletest') . '/css/simpletest.module.css';
_drupal_add_css($css);
$styles = drupal_get_css(NULL, FALSE, FALSE);
$this->assertTrue(strpos($styles, $css) > 0, 'Rendered CSS includes the added stylesheet.');
// Verify that newlines are properly added inside style tags.
$query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
$css_processed = '<link rel="stylesheet" href="' . String::checkPlain(file_create_url($css)) . "?" . $query_string . '" media="all" />';
$this->assertEqual(trim($styles), $css_processed, 'Rendered CSS includes newlines inside style tags for JavaScript use.');
}
/**
* Tests rendering an external stylesheet.
*/
function testRenderExternal() {
$css = 'http://example.com/style.css';
_drupal_add_css($css, 'external');
$styles = drupal_get_css();
// Stylesheet URL may be the href of a LINK tag or in an @import statement
// of a STYLE tag.
$this->assertTrue(strpos($styles, 'href="' . $css) > 0 || strpos($styles, '@import url("' . $css . '")') > 0, 'Rendering an external CSS file.');
}
/**
* Tests rendering inline stylesheets with preprocessing on.
*/
function testRenderInlinePreprocess() {
// Turn on CSS aggregation to allow for preprocessing.
$config = $this->container->get('config.factory')->get('system.performance');
$config->set('css.preprocess', 1);
$css = 'body { padding: 0px; }';
$css_preprocessed = '<style media="all">' . "\n/* <![CDATA[ */\n" . "body{padding:0px;}\n" . "\n/* ]]> */\n" . '</style>';
_drupal_add_css($css, array('type' => 'inline'));
$styles = drupal_get_css(NULL, NULL, FALSE);
$this->assertEqual(trim($styles), $css_preprocessed, 'Rendering preprocessed inline CSS adds it to the page.');
}
/**
* Tests rendering inline stylesheets with preprocessing off.
*/
function testRenderInlineNoPreprocess() {
$css = 'body { padding: 0px; }';
_drupal_add_css($css, array('type' => 'inline', 'preprocess' => FALSE));
$styles = drupal_get_css();
$this->assertTrue(strpos($styles, $css) > 0, 'Rendering non-preprocessed inline CSS adds it to the page.');
}
/**
* Tests CSS ordering.
*/
function testRenderOrder() {
// Load a module CSS file.
_drupal_add_css(drupal_get_path('module', 'simpletest') . '/css/simpletest.module.css');
// Load a few system CSS files in a custom, early-loading aggregate group.
$test_aggregate_group = -100;
$system_path = drupal_get_path('module', 'system');
_drupal_add_css($system_path . '/css/system.module.css', array('group' => $test_aggregate_group, 'weight' => -10));
_drupal_add_css($system_path . '/css/system.theme.css', array('group' => $test_aggregate_group));
$expected = array(
$system_path . '/css/system.module.css',
$system_path . '/css/system.theme.css',
drupal_get_path('module', 'simpletest') . '/css/simpletest.module.css',
);
$styles = drupal_get_css(NULL, NULL, FALSE);
// Stylesheet URL may be the href of a LINK tag or in an @import statement
// of a STYLE tag.
if (preg_match_all('/(href="|url\(")' . preg_quote($GLOBALS['base_url'] . '/', '/') . '([^?]+)\?/', $styles, $matches)) {
$result = $matches[2];
}
else {
$result = array();
}
$this->assertIdentical($result, $expected, 'The CSS files are in the expected order.');
}
/**
* Tests CSS override.
*/
function testRenderOverride() {
$system = drupal_get_path('module', 'system');
_drupal_add_css($system . '/css/system.module.css');
_drupal_add_css($system . '/tests/css/system.module.css');
// The dummy stylesheet should be the only one included.
$styles = drupal_get_css();
$this->assert(strpos($styles, $system . '/tests/css/system.module.css') !== FALSE, 'The overriding CSS file is output.');
$this->assert(strpos($styles, $system . '/css/system.module.css') === FALSE, 'The overridden CSS file is not output.');
_drupal_add_css($system . '/tests/css/system.module.css');
_drupal_add_css($system . '/css/system.module.css');
// The standard stylesheet should be the only one included.
$styles = drupal_get_css();
$this->assert(strpos($styles, $system . '/css/system.module.css') !== FALSE, 'The overriding CSS file is output.');
$this->assert(strpos($styles, $system . '/tests/css/system.module.css') === FALSE, 'The overridden CSS file is not output.');
}
/**
* Tests that CSS query string remains intact when added to file.
*/
function testAddCssFileWithQueryString() {
$css_without_query_string = drupal_get_path('module', 'node') . '/css/node.admin.css';
$css_with_query_string = '/' . drupal_get_path('module', 'node') . '/node-fake.css?arg1=value1&arg2=value2';
_drupal_add_css($css_without_query_string);
_drupal_add_css($css_with_query_string);
$styles = drupal_get_css();
$query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
$this->assertTrue(strpos($styles, $css_without_query_string . '?' . $query_string), 'Query string was appended correctly to css.');
$this->assertTrue(strpos($styles, str_replace('&', '&amp;', $css_with_query_string)), 'Query string not escaped on a URI.');
}
}
......@@ -66,100 +66,6 @@ function testLibraryMerging() {
$this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly; duplicates are retained.');
}
/**
* Tests justs CSS asset merging.
*/
function testCssMerging() {
$a['#attached'] = array(
'css' => array(
'foo.css' => array(),
'bar.css' => array(),
),
);
$b['#attached'] = array(
'css' => array(
'baz.css' => array(),
),
);
$expected['#attached'] = array(
'css' => array(
'foo.css' => array(),
'bar.css' => array(),
'baz.css' => array(),
),
);
$this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly.');
// Merging in the opposite direction yields the opposite CSS asset order.
$expected['#attached'] = array(
'css' => array(
'baz.css' => array(),
'foo.css' => array(),
'bar.css' => array(),
),
);
$this->assertIdentical($expected['#attached'], drupal_merge_attached($b['#attached'], $a['#attached']), 'Attachments merged correctly; opposite merging yields opposite order.');
// Merging with duplicates: duplicates are automatically removed because the
// values have unique keys.
$b['#attached']['css']['bar.css'] = array();
$expected['#attached'] = array(
'css' => array(
'foo.css' => array(),
'bar.css' => array(),
'baz.css' => array(),
),
);
$this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly; CSS asset duplicates removed.');
}
/**
* Tests justs JavaScript asset merging.
*/
function testJsMerging() {
$a['#attached'] = array(
'js' => array(
'foo.js' => array(),
'bar.js' => array(),
),
);
$b['#attached'] = array(
'js' => array(
'baz.js' => array(),
),
);
$expected['#attached'] = array(
'js' => array(
'foo.js' => array(),
'bar.js' => array(),
'baz.js' => array(),
),
);
$this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly.');
// Merging in the opposite direction yields the opposite JS asset order.
$expected['#attached'] = array(
'js' => array(
'baz.js' => array(),
'foo.js' => array(),
'bar.js' => array(),
),
);
$this->assertIdentical($expected['#attached'], drupal_merge_attached($b['#attached'], $a['#attached']), 'Attachments merged correctly; opposite merging yields opposite order.');
// Merging with duplicates: duplicates are automatically removed because the
// values have unique keys.
$b['#attached']['js']['bar.js'] = array();
$expected['#attached'] = array(
'js' => array(
'foo.js' => array(),
'bar.js' => array(),
'baz.js' => array(),
),
);
$this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly; JS asset duplicates removed.');
}
/**
* Tests justs JavaScript and JavaScript setting asset merging.
*/
......
......@@ -354,40 +354,35 @@ function testDrupalRenderChildrenAttached() {
\Drupal::request()->setMethod('GET');
// Create an element with a child and subchild. Each element loads a
// different JavaScript file using #attached.
$parent_js = drupal_get_path('module', 'user') . '/user.js';
$child_js = drupal_get_path('module', 'forum') . '/forum.js';
$subchild_js = drupal_get_path('module', 'book') . '/book.js';
// different library using #attached.
$element = array(
'#type' => 'details',
'#open' => TRUE,
'#type' => 'container',
'#cache' => array(
'keys' => array('simpletest', 'drupal_render', 'children_attached'),
),
'#attached' => array('js' => array($parent_js)),
'#attached' => ['library' => ['test/parent']],
'#title' => 'Parent',
);
$element['child'] = array(
'#type' => 'details',
'#open' => TRUE,
'#attached' => array('js' => array($child_js)),
'#type' => 'container',
'#attached' => ['library' => ['test/child']],
'#title' => 'Child',
);
$element['child']['subchild'] = array(
'#attached' => array('js' => array($subchild_js)),
'#attached' => ['library' => ['test/subchild']],
'#markup' => 'Subchild',
);
// Render the element and verify the presence of #attached JavaScript.
drupal_render($element);
$expected_js = [$parent_js, $child_js, $subchild_js];
$this->assertEqual($element['#attached']['js'], $expected_js, 'The element, child and subchild #attached JavaScript are included.');
$expected_libraries = ['test/parent', 'test/child', 'test/subchild'];
$this->assertEqual($element['#attached']['library'], $expected_libraries, 'The element, child and subchild #attached libraries are included.');
// Load the element from cache and verify the presence of the #attached
// JavaScript.
$element = array('#cache' => array('keys' => array('simpletest', 'drupal_render', 'children_attached')));
$this->assertTrue(strlen(drupal_render($element)) > 0, 'The element was retrieved from cache.');
$this->assertEqual($element['#attached']['js'], $expected_js, 'The element, child and subchild #attached JavaScript are included.');
$this->assertEqual($element['#attached']['library'], $expected_libraries, 'The element, child and subchild #attached libraries are included.');
// Restore the previous request method.
\Drupal::request()->setMethod($request_method);
......
order:
drupalSettings:
ajax: test
dependencies:
- ajax_test/order-css-command
- ajax_test/order-js-command
order-css-command:
css:
theme:
# Two CSS files (order should remain the same).
a.css: {}
b.css: {}
order-js-command:
js:
# Two JavaScript files (first to the footer, should appear last).
footer.js: { scope: footer }
header.js: {}
......@@ -47,29 +47,10 @@ function ajax_test_render() {
*/
function ajax_test_order() {
$response = new AjaxResponse();
$path = drupal_get_path('module', 'system');
// HTML insertion command.
$response->addCommand(new HtmlCommand('body', 'Hello, world!'));
$attached = array(
'#attached' => array(
'css' => array(
// Add two CSS files (order should remain the same).
$path . '/css/system.admin.css' => array(),
$path . '/css/system.maintenance.css' => array(),
),
'js' => array(
// Add two JavaScript files (first to the footer, should appear last).
$path . '/system.modules.js' => array('scope' => 'footer'),
$path . '/system.js' => array(),
),
// Finally, add a JavaScript setting.
'drupalSettings' => array(
'ajax' => 'test',
),
),
);
drupal_process_attached($attached);
$build['#attached']['library'][] = 'ajax_test/order';
drupal_process_attached($build);
return $response;
}
......
......@@ -7,3 +7,106 @@ jquery.farbtastic:
assets/vendor/farbtastic/farbtastic.css: {}
dependencies:
- core/jquery
# Library to test CSS and JS file assets.
files:
js:
foo.js: {}
css:
theme:
bar.css: {}
# Library to test external CSS and JS file assets.
external:
version: 1
js:
http://example.com/script.js: { type: external }
css:
theme:
http://example.com/stylesheet.css: { type: external }
# Library to test JS file asset attributes (both internal and external).
js-attributes:
version: 1
js:
deferred-internal.js: { attributes: { defer: true, bar: foo } }
http://example.com/deferred-external.js:
type: external
attributes:
foo: bar
defer: true
js-footer:
js:
footer.js: { scope: footer }
# Library to test setting cache = FALSE, to prevent aggregation.
no-cache:
js:
nocache.js: { cache: false }
order:
js:
weight_-3_1.js: { weight: -3 }
weight_0_1.js: {}
weight_0_2.js: {}
weight_-8_1.js: { weight: -8 }
weight_-8_2.js: { weight: -8 }
weight_-8_3.js: { weight: -8 }
http://example.com/weight_-5_1.js: { type: external, weight: -5 }
weight_-8_4.js: { weight: -8 }
weight_-3_2.js: { weight: -3 }
weight_0_3.js: {}
css:
base:
base_weight_0_1.js: {}
base_weight_0_2.js: {}
base_weight_-8_1.js: { weight: -8 }
base_weight_-101_1.js: { weight: -101 }
layout:
layout_weight_0_1.js: {}
layout_weight_0_2.js: {}
layout_weight_-8_1.js: { weight: -8 }
layout_weight_-101_1.js: { weight: -101 }
component:
component_weight_0_1.js: {}
component_weight_0_2.js: {}
component_weight_-8_1.js: { weight: -8}
component_weight_-101_1.js: { weight: -101}
state:
state_weight_0_1.js: {}
state_weight_0_2.js: {}
state_weight_-8_1.js: { weight: -8}
state_weight_-101_1.js: { weight: -101}
theme:
theme_weight_0_1.js: {}
theme_weight_0_2.js: {}
theme_weight_-8_1.js: { weight: -8}
theme_weight_-101_1.js: { weight: -101}
weight:
css:
theme:
first.css: {}
lighter.js: { weight: -1 }
js:
first.js: {}
lighter.js: { weight: -1 }
before-jquery.js: { weight: -21 }
browsers:
js:
old-ie.js: