Commit 327fdc1b authored by Benni Mack's avatar Benni Mack Committed by Oliver Hader
Browse files

[!!!][FEATURE] Omit type=text/javascript in HTML5 script tags

HTML5 defines that <script tags do not need "type=text/javascript"
as additional attribute.

TYPO3 Backend is fully HTML5, so all parts can be removed there.

For Frontend, when having config.doctype = html5 (or empty),
then the attributes do not get added anymore as well.

If necessary, for Frontend rendering the attribute can be
added in HTML5 by specifying
includeJS.myfile.type = text/javascript
in TypoScript.

As this modifies Frontend output, it is considered breaking.

Also see W3C specification:
https://www.w3.org/TR/html52/semantics-scripting.html#element-attrdef-script-type

Resolves: #88772
Releases: master
Change-Id: I26ca4361e84cae680eedbf6855e209a6311c33da
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/61300


Reviewed-by: Markus Klein's avatarMarkus Klein <markus.klein@typo3.org>
Reviewed-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Reviewed-by: Oliver Klee's avatarOliver Klee <typo3-coding@oliverklee.de>
Reviewed-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Tested-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
parent 0b0f5f1e
......@@ -111,7 +111,7 @@ class ResourceUtility
*/
protected static function getJsTag(string $jsFileLocation): string
{
$js = '<script type="text/javascript" src="' .
$js = '<script src="' .
htmlspecialchars(
PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($jsFileLocation)),
ENT_QUOTES | ENT_HTML5
......
......@@ -59,7 +59,7 @@ class EditController extends AbstractWizardController
*
* @var string
*/
protected $closeWindow = '<script language="javascript" type="text/javascript">close();</script>';
protected $closeWindow = '<script>close();</script>';
/**
* Injects the request object for the current request or subrequest
......
......@@ -5,7 +5,7 @@
<f:form.hidden name="defValues" value="" />
<f:if condition="{onClickEvent}">
<f:then>
<script type="text/javascript">
<script>
function goToalt_doc() {<f:format.raw>{onClickEvent}</f:format.raw>}
</script>
{renderedTabs -> f:format.raw()}
......
......@@ -5,7 +5,7 @@
<!-- TYPO3 Script ID: typo3/sysext/backend/Resources/Public/Html/Close.html -->
<meta charset="utf-8" />
<title>Close</title>
<script type="text/javascript">
<script>
self.close();
window.opener.location.reload(true);
</script>
......
......@@ -96,7 +96,7 @@
</p>
<p><f:translate key="LLL:EXT:beuser/Resources/Private/Language/locallang_mod_permission.xlf:def" /></p>
<script type="text/javascript">
<script>
require(["jquery", "TYPO3/CMS/Beuser/Permissions"], function($, Permissions) {
Permissions.setCheck("check[perms_user]", "tx_beuser_system_beusertxpermission[data][pages][{id}][perms_user]");
Permissions.setCheck("check[perms_group]", "tx_beuser_system_beusertxpermission[data][pages][{id}][perms_group]");
......
......@@ -1019,11 +1019,8 @@ class PageRenderer implements SingletonInterface
* @param bool $defer Flag if property 'defer="defer"' should be added to JavaScript tags
* @param string $crossorigin CORS settings attribute
*/
public function addJsLibrary($name, $file, $type = 'text/javascript', $compress = false, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '')
public function addJsLibrary($name, $file, $type = '', $compress = false, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '')
{
if (!$type) {
$type = 'text/javascript';
}
if (!in_array(strtolower($name), $this->jsLibs)) {
$this->jsLibs[strtolower($name)] = [
'file' => $file,
......@@ -1058,11 +1055,8 @@ class PageRenderer implements SingletonInterface
* @param bool $defer Flag if property 'defer="defer"' should be added to JavaScript tags
* @param string $crossorigin CORS settings attribute
*/
public function addJsFooterLibrary($name, $file, $type = 'text/javascript', $compress = false, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '')
public function addJsFooterLibrary($name, $file, $type = '', $compress = false, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '')
{
if (!$type) {
$type = 'text/javascript';
}
$name .= '_jsFooterLibrary';
if (!in_array(strtolower($name), $this->jsLibs)) {
$this->jsLibs[strtolower($name)] = [
......@@ -1097,11 +1091,8 @@ class PageRenderer implements SingletonInterface
* @param bool $defer Flag if property 'defer="defer"' should be added to JavaScript tags
* @param string $crossorigin CORS settings attribute
*/
public function addJsFile($file, $type = 'text/javascript', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '')
public function addJsFile($file, $type = '', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '')
{
if (!$type) {
$type = 'text/javascript';
}
if (!isset($this->jsFiles[$file])) {
$this->jsFiles[$file] = [
'file' => $file,
......@@ -1135,11 +1126,8 @@ class PageRenderer implements SingletonInterface
* @param bool $defer Flag if property 'defer="defer"' should be added to JavaScript tags
* @param string $crossorigin CORS settings attribute
*/
public function addJsFooterFile($file, $type = 'text/javascript', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '')
public function addJsFooterFile($file, $type = '', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '')
{
if (!$type) {
$type = 'text/javascript';
}
if (!isset($this->jsFiles[$file])) {
$this->jsFiles[$file] = [
'file' => $file,
......@@ -1448,14 +1436,14 @@ class PageRenderer implements SingletonInterface
// directly after that, include the require.js file
$html .= '<script src="'
. $this->processJsFile($this->requireJsPath . 'require.js')
. '" type="text/javascript"></script>' . LF;
. '"></script>' . LF;
if (!empty($requireJsConfig['typo3BaseUrl'])) {
$html .= '<script src="'
. $this->processJsFile(
'EXT:core/Resources/Public/JavaScript/requirejs-loader.js'
)
. '" type="text/javascript"></script>' . LF;
. '"></script>' . LF;
}
return $html;
......@@ -2165,11 +2153,12 @@ class PageRenderer implements SingletonInterface
if (!empty($this->jsFiles)) {
foreach ($this->jsFiles as $file => $properties) {
$file = $this->getStreamlinedFileName($file);
$type = $properties['type'] ? ' type="' . htmlspecialchars($properties['type']) . '"' : '';
$async = $properties['async'] ? ' async="async"' : '';
$defer = $properties['defer'] ? ' defer="defer"' : '';
$integrity = $properties['integrity'] ? ' integrity="' . htmlspecialchars($properties['integrity']) . '"' : '';
$crossorigin = $properties['crossorigin'] ? ' crossorigin="' . htmlspecialchars($properties['crossorigin']) . '"' : '';
$tag = '<script src="' . htmlspecialchars($file) . '" type="' . htmlspecialchars($properties['type']) . '"' . $async . $defer . $integrity . $crossorigin . '></script>';
$tag = '<script src="' . htmlspecialchars($file) . '"' . $type . $async . $defer . $integrity . $crossorigin . '></script>';
if ($properties['allWrap']) {
$wrapArr = explode($properties['splitChar'] ?: '|', $properties['allWrap'], 2);
$tag = $wrapArr[0] . $tag . $wrapArr[1];
......
......@@ -1324,7 +1324,7 @@ class GeneralUtility
if (preg_match('/^(\\t+)/', $string, $match)) {
$string = str_replace($match[1], "\t", $string);
}
return '<script type="text/javascript">
return '<script>
/*<![CDATA[*/
' . $string . '
/*]]>*/
......
.. include:: ../../Includes.txt
============================================================================
Breaking: #88772 - JavaScript script tags omit type=text/javascript in HTML5
============================================================================
See :issue:`88772`
Description
===========
When rendering HTML5 output, `<script>` tags do not the additional attribute `type=text/javascript`
anymore as it is considered optional, and if none given, modern browsers fall back to this type
already.
See the official W3C definition here: https://www.w3.org/TR/html52/semantics-scripting.html#element-attrdef-script-type
For this reason, all of TYPO3's Backend (which is rendering HTML5) and Installer do not include
this optional attribute in `<script>` tags anymore.
For TYPO3 Frontend rendering, the attribute is omitted when having no doctype or HTML5 as doctype
configured (via TypoScript :typoscript:`config.doctype = html5`). This leads to a minimal smaller
HTML document submitted to the client.
For any XHTML or HTML4-based website, the attribute is still added.
Impact
======
TYPO3's Frontend rendering does not render `type=text/javascript` anymore in `<script>` tags when
rendering a HTML5 output, unless explicitly specified.
Affected Installations
======================
Any TYPO3 installation running a HTML5-based frontend output.
Migration
=========
As all modern browsers do not need this tag, and the specification says it's optional, there is
no migration needed at all.
If still requested by a specific project, it can be added via:
.. code-block:: javascript
page.includeJS.myfile = EXT:site_mysite/Resources/Public/JavaScript/myfile.js
page.includeJS.myfile.type = text/javascript
.. index:: Frontend, TypoScript, NotScanned
\ No newline at end of file
......@@ -74,6 +74,9 @@ class PageRendererTest extends \TYPO3\TestingFramework\Core\Functional\Functiona
$subject->addJsFile('fileadmin/test.js', 'text/javascript', false, false, 'wrapBeforeXwrapAfter', false, 'X');
$expectedJsFileRegExp = '#wrapBefore<script src="fileadmin/test\\.(js|\\d+\\.js|js\\?\\d+)" type="text/javascript"></script>wrapAfter#';
$subject->addJsFile('fileadmin/test-plain.js', '', false, false, 'wrapBeforeXwrapAfter', false, 'X');
$expectedJsFileWithoutTypeRegExp = '#wrapBefore<script src="fileadmin/test-plain\\.(js|\\d+\\.js|js\\?\\d+)"></script>wrapAfter#';
$jsInlineCode = $expectedJsInlineCodeString = 'var x = "' . $this->getUniqueId('jsInline-') . '"';
$subject->addJsInlineCode($this->getUniqueId(), $jsInlineCode);
......@@ -99,6 +102,7 @@ class PageRendererTest extends \TYPO3\TestingFramework\Core\Functional\Functiona
$this->assertStringContainsString($expectedHeaderData, $renderedString);
$this->assertRegExp($expectedJsLibraryRegExp, $renderedString);
$this->assertRegExp($expectedJsFileRegExp, $renderedString);
$this->assertRegExp($expectedJsFileWithoutTypeRegExp, $renderedString);
$this->assertStringContainsString($expectedJsInlineCodeString, $renderedString);
$this->assertStringContainsString($expectedCssFileString, $renderedString);
$this->assertStringContainsString($expectedCssInlineBlockOnTopString, $renderedString);
......
......@@ -267,7 +267,7 @@ class Response extends \TYPO3\CMS\Extbase\Mvc\Response
/**
* Adds an additional header data (something like
* '<script src="myext/Resources/JavaScript/my.js" type="text/javascript"></script>'
* '<script src="myext/Resources/JavaScript/my.js"></script>'
* )
*
* @TODO The workround and the $request member should be removed again, once the PageRender does support non-cached USER_INTs
......
......@@ -21,8 +21,8 @@ use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
* .. note::
* Make sure to include jQuery and jQuery UI in the HTML, like that::
*
* <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
* <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.4/jquery-ui.min.js"></script>
* <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
* <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.4/jquery-ui.min.js"></script>
* <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.3/themes/base/jquery-ui.css" type="text/css" media="all" />
* <link rel="stylesheet" href="http://static.jquery.com/ui/css/demo-docs-theme/ui.theme.css" type="text/css" media="all" />
*
......
......@@ -3,7 +3,7 @@
</f:if>
<f:if condition="{configuration.insertAbove} || {configuration.insertBelow}">
<script type="text/javascript">
<script>
function goToPage(formObject) {
var formField = formObject.elements['paginator-target-page'];
var url = formField.dataset.url;
......
......@@ -25,7 +25,7 @@
<f:render section="Inspector" />
</section>
</div>
<script type="text/javascript">
<script>
require(['{dynamicRequireJsModules.app}', '{dynamicRequireJsModules.mediator}', '{dynamicRequireJsModules.viewModel}'], function (formEditorApp, mediator, viewModel) {
window.TYPO3.FORMEDITOR_APP = formEditorApp.getInstance(
<f:format.htmlentitiesDecode>{formEditorAppInitialData}</f:format.htmlentitiesDecode>,
......
......@@ -2,7 +2,7 @@
<f:layout name="FormManager" />
<f:section name="MainContent">
<script type="text/javascript">
<script>
require(['{dynamicRequireJsModules.app}', '{dynamicRequireJsModules.viewModel}'], function (formManagerApp, viewModel) {
var FORMMANAGER_APP = formManagerApp.getInstance(
<f:format.htmlentitiesDecode>{formManagerAppInitialData}</f:format.htmlentitiesDecode>,
......
......@@ -2907,8 +2907,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface
: $this->additionalJavaScript;
$additionalJavaScript = trim($additionalJavaScript);
if ($jsCode !== '' || $additionalJavaScript !== '') {
$doctype = $controller->config['config']['doctype'] ?? 'html5';
$scriptAttribute = $doctype === 'html5' ? '' : ' type="text/javascript"';
$this->additionalHeaderData['JSCode'] = '
<script type="text/javascript">
<script' . $scriptAttribute . '>
/*<![CDATA[*/
<!--
' . $additionalJavaScript . '
......
......@@ -247,6 +247,7 @@ class RequestHandler implements RequestHandlerInterface
}
// Part 2: DTD
$doctype = $controller->config['config']['doctype'] ?? null;
$defaultTypeAttributeForJavaScript = 'text/javascript';
if ($doctype) {
switch ($doctype) {
case 'xhtml_trans':
......@@ -275,6 +276,7 @@ class RequestHandler implements RequestHandlerInterface
"http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">';
break;
case 'html5':
$defaultTypeAttributeForJavaScript = '';
$docTypeParts[] = '<!DOCTYPE html>';
if ($xmlDocument) {
$pageRenderer->setMetaCharsetTag('<meta charset="|" />');
......@@ -294,6 +296,7 @@ class RequestHandler implements RequestHandlerInterface
} else {
$pageRenderer->setMetaCharsetTag('<meta charset="|">');
}
$defaultTypeAttributeForJavaScript = '';
}
if ($htmlLang) {
if ($controller->xhtmlVersion) {
......@@ -497,10 +500,7 @@ class RequestHandler implements RequestHandlerInterface
}
if ($ss) {
$jsFileConfig = &$controller->pSetup['includeJSLibs.'][$key . '.'];
$type = $jsFileConfig['type'];
if (!$type) {
$type = 'text/javascript';
}
$type = $jsFileConfig['type'] ?? $defaultTypeAttributeForJavaScript;
$crossOrigin = $jsFileConfig['crossorigin'];
if (!$crossOrigin && $jsFileConfig['integrity'] && $jsFileConfig['external']) {
$crossOrigin = 'anonymous';
......@@ -541,10 +541,7 @@ class RequestHandler implements RequestHandlerInterface
}
if ($ss) {
$jsFileConfig = &$controller->pSetup['includeJSFooterlibs.'][$key . '.'];
$type = $jsFileConfig['type'];
if (!$type) {
$type = 'text/javascript';
}
$type = $jsFileConfig['type'] ?? $defaultTypeAttributeForJavaScript;
$crossorigin = $jsFileConfig['crossorigin'];
if (!$crossorigin && $jsFileConfig['integrity'] && $jsFileConfig['external']) {
$crossorigin = 'anonymous';
......@@ -586,10 +583,7 @@ class RequestHandler implements RequestHandlerInterface
}
if ($ss) {
$jsConfig = &$controller->pSetup['includeJS.'][$key . '.'];
$type = $jsConfig['type'];
if (!$type) {
$type = 'text/javascript';
}
$type = $jsConfig['type'] ?? $defaultTypeAttributeForJavaScript;
$crossorigin = $jsConfig['crossorigin'];
if (!$crossorigin && $jsConfig['integrity'] && $jsConfig['external']) {
$crossorigin = 'anonymous';
......@@ -629,10 +623,7 @@ class RequestHandler implements RequestHandlerInterface
}
if ($ss) {
$jsConfig = &$controller->pSetup['includeJSFooter.'][$key . '.'];
$type = $jsConfig['type'];
if (!$type) {
$type = 'text/javascript';
}
$type = $jsConfig['type'] ?? $defaultTypeAttributeForJavaScript;
$crossorigin = $jsConfig['crossorigin'];
if (!$crossorigin && $jsConfig['integrity'] && $jsConfig['external']) {
$crossorigin = 'anonymous';
......@@ -802,7 +793,7 @@ class RequestHandler implements RequestHandlerInterface
$pageRenderer->addJsInlineCode('TS_inlineJSint', $inlineJSint, $controller->config['config']['compressJs']);
}
if (trim($scriptJsCode . $inlineJS)) {
$pageRenderer->addJsFile(GeneralUtility::writeJavaScriptContentToTemporaryFile($scriptJsCode . $inlineJS), 'text/javascript', $controller->config['config']['compressJs']);
$pageRenderer->addJsFile(GeneralUtility::writeJavaScriptContentToTemporaryFile($scriptJsCode . $inlineJS), $defaultTypeAttributeForJavaScript, $controller->config['config']['compressJs']);
}
if ($inlineFooterJs) {
$inlineFooterJSint = '';
......@@ -810,7 +801,7 @@ class RequestHandler implements RequestHandlerInterface
if ($inlineFooterJSint) {
$pageRenderer->addJsFooterInlineCode('TS_inlineFooterJSint', $inlineFooterJSint, $controller->config['config']['compressJs']);
}
$pageRenderer->addJsFooterFile(GeneralUtility::writeJavaScriptContentToTemporaryFile($inlineFooterJs), 'text/javascript', $controller->config['config']['compressJs']);
$pageRenderer->addJsFooterFile(GeneralUtility::writeJavaScriptContentToTemporaryFile($inlineFooterJs), $defaultTypeAttributeForJavaScript, $controller->config['config']['compressJs']);
}
} else {
// Include only inlineJS
......
......@@ -20,7 +20,7 @@
<script src="typo3temp/assets/js/175aa1ef20.js?1340313498" type="text/javascript"></script>
<script src="typo3temp/assets/js/175aa1ef20.js?1340313498"></script>
<!--HD_679b52796e75d474ccbbed486b6837ab-->
......
......@@ -115,7 +115,7 @@ class TypoScriptConstantsViewHelper extends AbstractTagBasedViewHelper
<div class="form-wizards-element">
<input class="form-control t3js-color-input formengine-colorpickerelement t3js-color-picker" type="text"
name="' . htmlspecialchars($elementName) . '" value="' . $this->tag->getAttribute('value') . '"/>
<script type="text/javascript">
<script>
require([\'TYPO3/CMS/Backend/ColorPicker\'], function(ColorPicker){ColorPicker.initialize()});
</script>
</div>';
......
......@@ -9,9 +9,9 @@
<script>
var __bust = '{bust}';
</script>
<script type="text/javascript" src="{f:uri.resource(path: 'JavaScript/RequireJSConfig.js')}?{bust}"></script>
<script type="text/javascript" src="{f:uri.resource(path: 'JavaScript/Contrib/require.js', extensionName: 'Core')}?{bust}"></script>
<script type="text/javascript">
<script src="{f:uri.resource(path: 'JavaScript/RequireJSConfig.js')}?{bust}"></script>
<script src="{f:uri.resource(path: 'JavaScript/Contrib/require.js', extensionName: 'Core')}?{bust}"></script>
<script>
require(['TYPO3/CMS/Install/Installer']);
</script>
</head>
......
......@@ -10,9 +10,9 @@
var TYPO3 = TYPO3 || {};
var __bust = '{bust}';
</script>
<script type="text/javascript" src="{f:uri.resource(path: 'JavaScript/RequireJSConfig.js')}?{bust}"></script>
<script type="text/javascript" src="{f:uri.resource(path: 'JavaScript/Contrib/require.js', extensionName: 'Core')}?{bust}"></script>
<script type="text/javascript">
<script src="{f:uri.resource(path: 'JavaScript/RequireJSConfig.js')}?{bust}"></script>
<script src="{f:uri.resource(path: 'JavaScript/Contrib/require.js', extensionName: 'Core')}?{bust}"></script>
<script>
require(['TYPO3/CMS/Install/Install']);
</script>
</head>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment