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()) ); }