diff --git a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php
index 80f6fcbfc1b54..ecc3696a6767c 100644
--- a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php
+++ b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php
@@ -330,7 +330,7 @@ protected function getGroupAttributes($group)
if (is_array($attributes)) {
$attributesString = '';
foreach ($attributes as $name => $value) {
- $attributesString .= ' ' . $name . '="' . $this->escaper->escapeHtml($value) . '"';
+ $attributesString .= ' ' . $name . '="' . $this->escaper->escapeHtmlAttr($value) . '"';
}
$attributes = $attributesString;
} else {
@@ -344,43 +344,77 @@ protected function getGroupAttributes($group)
* Add default attributes
*
* @param string $contentType
- * @param string $attributes
- * @return string
+ * @param array|null $attributes
+ * @return array
*/
protected function addDefaultAttributes($contentType, $attributes)
{
+ $attributes = is_array($attributes)
+ ? $attributes
+ : [];
+ $defaultAttributes = [];
+
if ($contentType === 'js') {
- return ' type="text/javascript" ' . $attributes;
+ $defaultAttributes['type'] = 'text/javascript';
}
if ($contentType === 'css') {
- return ' rel="stylesheet" type="text/css" ' . ($attributes ?: ' media="all"');
+ $defaultAttributes['rel'] = 'stylesheet';
+ $defaultAttributes['type'] = 'text/css';
+
+ if (empty($attributes)) {
+ $attributes['media'] = 'all';
+ }
+
+ // add defer attributes when defer parameter is exist
+ if (array_key_exists('defer', $attributes)) {
+ $defaultAttributes['rel'] = 'preload';
+ $defaultAttributes['as'] = 'style';
+ $defaultAttributes['onload'] = 'this.onload=null;this.rel=\'stylesheet\';';
+ }
}
if ($this->canTypeBeFont($contentType)) {
- return 'rel="preload" as="font" crossorigin="anonymous"';
+ $defaultAttributes['rel'] = 'preload';
+ $defaultAttributes['as'] = 'font';
+ $defaultAttributes['crossorigin'] = 'anonymous';
}
- return $attributes;
+ return array_merge($defaultAttributes, $attributes);
}
/**
* Returns assets template
*
* @param string $contentType
- * @param string|null $attributes
+ * @param array|null $attributes
* @return string
*/
protected function getAssetTemplate($contentType, $attributes)
{
+ $attributesString = '';
+ foreach ($attributes as $name => $value) {
+
+ // don't add defer attribute to css output
+ if ($contentType === 'css' && $name === 'defer') {
+ continue;
+ }
+
+ $attributesString .= ' ' . $name . '="' . $this->escaper->escapeHtmlAttr($value) . '"';
+ }
+
switch ($contentType) {
case 'js':
- $groupTemplate = '' . "\n";
+ $groupTemplate = '' . "\n";
break;
case 'css':
default:
- $groupTemplate = '' . "\n";
+ $groupTemplate = '' . "\n";
+
+ if (array_key_exists('defer', $attributes)) {
+ $groupTemplate .= '' . "\n";
+ }
break;
}
return $groupTemplate;
@@ -411,7 +445,7 @@ protected function processIeCondition($groupHtml, $group)
protected function renderAssetHtml(\Magento\Framework\View\Asset\PropertyGroup $group)
{
$assets = $this->processMerge($group->getAll(), $group);
- $attributes = $this->getGroupAttributes($group);
+ $attributes = $group->getProperty('attributes');
$result = '';
$template = '';
diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php
index d28358e177d07..3e1cf2f63bf2f 100644
--- a/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php
+++ b/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php
@@ -112,7 +112,7 @@ protected function setUp(): void
->disableOriginalConstructor()
->getMock();
$this->escaperMock->expects($this->any())
- ->method('escapeHtml')
+ ->method('escapeHtmlAttr')
->willReturnArgument(0);
$this->stringMock = $this->getMockBuilder(StringUtils::class)
@@ -425,41 +425,50 @@ public function dataProviderRenderAssets(): array
{
return [
[
- ['type' => 'css', 'attributes' => '', 'condition' => null],
- ['type' => 'js', 'attributes' => 'attr="value"', 'condition' => null],
- '' . "\n"
- . '' . "\n"
- . '' . "\n"
+ ['type' => 'css', 'attributes' => [], 'condition' => null],
+ ['type' => 'js', 'attributes' => ['attr' => 'value'], 'condition' => null],
+ '' . "\n"
+ . '' . "\n"
+ . '' . "\n"
+ ],
+ [
+ ['type' => 'css', 'attributes' => ['defer' => "true"], 'condition' => null],
+ ['type' => 'js', 'attributes' => ['defer' => "true"], 'condition' => null],
+ '' . "\n" //phpcs:ignore
+ . '' . "\n"
+ . '' . "\n" //phpcs:ignore
+ . '' . "\n"
+ . '' . "\n"
],
[
['type' => 'js', 'attributes' => ['attr' => 'value'], 'condition' => 'lt IE 7'],
- ['type' => 'css', 'attributes' => 'attr="value"', 'condition' => null],
- '' . "\n"
- . '' . "\n"
+ ['type' => 'css', 'attributes' => ['attr' => 'value'], 'condition' => null],
+ '' . "\n"
+ . '' . "\n"
],
[
- ['type' => 'ico', 'attributes' => 'attr="value"', 'condition' => null],
- ['type' => 'css', 'attributes' => '', 'condition' => null],
- '' . "\n"
- . '' . "\n"
- . '' . "\n"
+ ['type' => 'ico', 'attributes' => ['attr' => 'value'], 'condition' => null],
+ ['type' => 'css', 'attributes' => [], 'condition' => null],
+ '' . "\n"
+ . '' . "\n"
+ . '' . "\n"
],
[
- ['type' => 'js', 'attributes' => '', 'condition' => null],
+ ['type' => 'js', 'attributes' => [], 'condition' => null],
['type' => 'ico', 'attributes' => ['attr' => 'value'], 'condition' => null],
- '' . "\n"
- . '' . "\n"
- . '' . "\n"
+ '' . "\n"
+ . '' . "\n"
+ . '' . "\n"
],
[
['type' => 'non', 'attributes' => ['attr' => 'value'], 'condition' => null],
- ['type' => 'ico', 'attributes' => '', 'condition' => null],
+ ['type' => 'ico', 'attributes' => [], 'condition' => null],
'' . "\n"
- . '' . "\n"
- . '' . "\n"
+ . '' . "\n"
+ . '' . "\n"
]
];
}
@@ -492,7 +501,7 @@ public function testRenderAssetWithNoContentType() : void
[
[GroupedCollection::PROPERTY_CAN_MERGE, true],
[GroupedCollection::PROPERTY_CONTENT_TYPE, $type],
- ['attributes', 'rel="some-rel"'],
+ ['attributes', ['rel' => 'some-rel']],
['ie_condition', null]
]
);
@@ -506,7 +515,7 @@ public function testRenderAssetWithNoContentType() : void
->willReturn([$groupMockOne]);
$this->assertEquals(
- '' . "\n",
+ '' . "\n",
$this->renderer->renderAssets($this->renderer->getAvailableResultGroups())
);
}