From: Aryeh Gregor Date: Tue, 11 Aug 2009 00:09:24 +0000 (+0000) Subject: For HTML 5, drop type="" attributes for CSS/JS X-Git-Tag: 1.31.0-rc.0~40367 X-Git-Url: https://git.heureux-cyclage.org/?a=commitdiff_plain;h=7aa4a8f90cae08e52b5976e23a9971971c2ac36c;p=lhc%2Fweb%2Fwiklou.git For HTML 5, drop type="" attributes for CSS/JS This time done in a nice, centralized fashion, reducing LOC for callers even if HTML 5 is disabled. The implementation is a new Html class, similar to Xml but intended to be HTML-specific from the beginning instead of half-heartedly attempting to provide generic XML services but actually with lots of HTML-specific stuff tacked on. As part of the new Html class, a global config option $wgWellFormedXml is added. It's set to true by default, but if set to false, the Html class will drop some things that HTML 5 doesn't require, like self-closing " />" syntax and attribute quotation marks (sometimes). --- diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 7e8118381d..7cc621f8c0 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -192,6 +192,9 @@ this. Was used when mwEmbed was going to be an extension. numbers outside the permitted ranges), etc. ** The summary attribute has been removed from tables of contents. summary is obsolete in HTML 5 and wasn't useful here anyway. +** Unnecessary type="" attribute removed for CSS and JS. +** If $wgWellFormedXml is set to false, some bytes will be shaved off of HTML + output by omitting some things like quotation marks where HTML 5 allows. * Added crop for inline images. === Bug fixes in 1.16 === diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index c03eeb314b..d81a1cb239 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -95,6 +95,7 @@ $wgAutoloadLocalClasses = array( 'HistoryBlobCurStub' => 'includes/HistoryBlob.php', 'HistoryBlob' => 'includes/HistoryBlob.php', 'HistoryBlobStub' => 'includes/HistoryBlob.php', + 'Html' => 'includes/Html.php', 'HTMLCacheUpdate' => 'includes/HTMLCacheUpdate.php', 'HTMLCacheUpdateJob' => 'includes/HTMLCacheUpdate.php', 'HTMLFileCache' => 'includes/HTMLFileCache.php', diff --git a/includes/ChangesList.php b/includes/ChangesList.php index 57314eeb88..844f687427 100644 --- a/includes/ChangesList.php +++ b/includes/ChangesList.php @@ -600,15 +600,13 @@ class EnhancedChangesList extends ChangesList { * @ return string */ public function beginRecentChangesList() { - global $wgStylePath, $wgJsMimeType, $wgStyleVersion; + global $wgStylePath, $wgStyleVersion; $this->rc_cache = array(); $this->rcMoveIndex = 0; $this->rcCacheIndex = 0; $this->lastdate = ''; $this->rclistOpen = false; - $script = Xml::tags( 'script', array( - 'type' => $wgJsMimeType, - 'src' => $wgStylePath . "/common/enhancedchanges.js?$wgStyleVersion" ), '' ); + $script = Html::linkedScript( $wgStylePath . "/common/enhancedchanges.js?$wgStyleVersion" ); return $script; } /** diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 08ac119e9b..f799f60ca1 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -907,6 +907,23 @@ $wgXhtmlDefaultNamespace = 'http://www.w3.org/1999/xhtml'; */ $wgHtml5 = true; +/** + * Should we try to make our HTML output well-formed XML? If set to false, + * output will be a few bytes shorter, and the HTML will arguably be more + * readable. If set to true, life will be much easier for the authors of + * screen-scraping bots, and the HTML will arguably be more readable. + * + * Setting this to false may omit quotation marks on some attributes, omit + * slashes from some self-closing tags, omit some ending tags, etc., where + * permitted by HTML 5. Setting it to true will not guarantee that all pages + * will be well-formed, although non-well-formed pages should be rare and it's + * a bug if you find one. Conversely, setting it to false doesn't mean that + * all XML-y constructs will be omitted, just that they might be. + * + * Because of compatibility with screen-scraping bots, and because it's + * controversial, this is currently left to true by default. + */ +$wgWellFormedXml = true; /** * Permit other namespaces in addition to the w3.org default. diff --git a/includes/EditPage.php b/includes/EditPage.php index 4aa603bf1e..2c6fba0153 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -2056,7 +2056,7 @@ END * @return string */ static function getEditToolbar() { - global $wgStylePath, $wgContLang, $wgLang, $wgJsMimeType; + global $wgStylePath, $wgContLang, $wgLang; /** * toolarray an array of arrays which each include the filename of @@ -2171,9 +2171,9 @@ END ) ); $toolbar = "
\n"; - $toolbar.=""; - $toolbar.="\n
"; + $toolbar .= "\n"; wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) ); diff --git a/includes/Html.php b/includes/Html.php new file mode 100644 index 0000000000..7a75aef47e --- /dev/null +++ b/includes/Html.php @@ -0,0 +1,217 @@ + elements. + */ +class Html { + # List of void elements from HTML 5, section 9.1.2 as of 2009-08-10 + private static $voidElements = array( + 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', + 'keygen', 'link', 'meta', 'param', 'source' + ); + + /** + * Returns an HTML element in a string. The major advantage here over + * manually typing out the HTML is that it will escape all attribute + * values. If you're hardcoding all the attributes, or there are none, you + * should probably type out the string yourself. + * + * This is quite similar to Xml::element(), but it implements some useful + * HTML-specific logic. For instance, there is no $allowShortTag + * parameter: the closing tag is magically omitted if $element has an empty + * content model. If $wgWellFormedXml is false, then a few bytes will be + * shaved off the HTML output as well. In the future, other HTML-specific + * features might be added, like allowing arrays for the values of + * attributes like class= and media=. + * + * One notable difference to Xml::element() is that $contents is *not* + * escaped. This means that Html::element() can be usefully nested, rather + * than using the rather clumsy Xml::openElement() and Xml::closeElement(). + * + * @param $element string The element's name, e.g., 'a' + * @param $attribs array Associative array of attributes, e.g., array( + * 'href' => 'http://www.mediawiki.org/' ). Values will be HTML-escaped. + * @param $contents string The raw HTML contents of the element: *not* + * escaped! + * @return string Raw HTML + */ + public static function element( $element, $attribs = array(), $contents = '' ) { + global $wgWellFormedXml; + $element = strtolower( $element ); + $start = "<$element" . self::expandAttributes( $attribs ); + if ( in_array( $element, self::$voidElements ) ) { + if ( $wgWellFormedXml ) { + return "$start />"; + } + return "$start>"; + } else { + return "$start>$contents"; + } + } + + /** + * Given an associative array of element attributes, generate a string + * to stick after the element name in HTML output. Like array( 'href' => + * 'http://www.mediawiki.org/' ) becomes something like + * ' href="http://www.mediawiki.org"'. Again, this is like + * Xml::expandAttributes(), but it implements some HTML-specific logic. + * For instance, it will omit quotation marks if $wgWellFormedXml is false. + * + * @param $attribs array Associative array of attributes, e.g., array( + * 'href' => 'http://www.mediawiki.org/' ). Values will be HTML-escaped. + * @return string HTML fragment that goes between element name and '>' + * (starting with a space if at least one attribute is output) + */ + public static function expandAttributes( $attribs ) { + global $wgWellFormedXml; + + $ret = ''; + foreach ( $attribs as $key => $value ) { + # See the "Attributes" section in the HTML syntax part of HTML 5, + # 9.1.2.3 as of 2009-08-10. Most attributes can have quotation + # marks omitted, but not all. (Although a literal " is not + # permitted, we don't check for that, since it will be escaped + # anyway.) + if ( $wgWellFormedXml || $value == '' + || preg_match( "/[ '=<>]/", $value ) ) { + $quote = '"'; + } else { + $quote = ''; + } + + # Apparently we need to entity-encode \n, \r, \t, although the spec + # doesn't mention that. Since we're doing strtr() anyway, and we + # don't need <> escaped here, we may as well not call + # htmlspecialchars(). FIXME: verify that we actually need to + # escape \n\r\t here, and explain why, exactly. + $ret .= " $key=$quote" . strtr( $value, array( + '&' => '&', + '"' => '"', + "\n" => ' ', + "\r" => ' ', + "\t" => ' ' + ) ) . $quote; + } + return $ret; + } + + /** + * Output a ' or (for + * XML) literal "]]>". + * + * @param $contents string JavaScript + * @return string Raw HTML + */ + public static function inlineScript( $contents ) { + global $wgHtml5, $wgJsMimeType; + + $attrs = array(); + if ( !$wgHtml5 ) { + $attrs['type'] = $wgJsMimeType; + $contents = "/**/"; + } + return self::element( 'script', $attrs, $contents ); + } + + /** + * Output a . + * + * @param $url string + * @return string Raw HTML + */ + public static function linkedScript( $url ) { + global $wgHtml5, $wgJsMimeType; + + $attrs = array( 'src' => $url ); + if ( !$wgHtml5 ) { + $attrs['type'] = $wgJsMimeType; + } + return self::element( 'script', $attrs ); + } + + /** + * Output a ' (admittedly unlikely). + * + * @param $contents string CSS + * @param $media mixed A media type string, like 'screen', or null for all + * media + * @return string Raw HTML + */ + public static function inlineStyle( $contents, $media = null ) { + global $wgHtml5; + + $attrs = array(); + if ( !$wgHtml5 ) { + # Technically we should probably add CDATA stuff here like with + # scripts, but in practice, stylesheets tend not to have + # problematic characters anyway. + $attrs['type'] = 'text/css'; + } + if ( $media !== null ) { + $attrs['media'] = $media; + } + return self::element( 'style', $attrs, $contents ); + } + + /** + * Output a linking to the given URL for the given + * media type (if any). + * + * @param $url string + * @param $media mixed A media type string, like 'screen', or null for all + * media + * @return string Raw HTML + */ + public static function linkedStyle( $url, $media = null ) { + global $wgHtml5; + + $attrs = array( 'rel' => 'stylesheet', 'href' => $url ); + if ( !$wgHtml5 ) { + $attrs['type'] = 'text/css'; + } + if ( $media !== null ) { + $attrs['media'] = $media; + } + return self::element( 'link', $attrs ); + } +} diff --git a/includes/Linker.php b/includes/Linker.php index bd306ce278..8497a8360b 100644 --- a/includes/Linker.php +++ b/includes/Linker.php @@ -1257,7 +1257,6 @@ class Linker { /** @todo document */ function tocList($toc) { - global $wgJsMimeType; $title = wfMsgHtml('toc') ; return '
' @@ -1266,13 +1265,13 @@ class Linker { # no trailing newline, script should not be wrapped in a # paragraph . "\n
" - . '\n"; + . Html::inlineScript( + 'if (window.showTocToggle) {' + . ' var tocShowText = "' . Xml::escapeJsString( wfMsg('showtoc') ) . '";' + . ' var tocHideText = "' . Xml::escapeJsString( wfMsg('hidetoc') ) . '";' + . ' showTocToggle();' + . ' } ' ) + . "\n"; } /** diff --git a/includes/OutputPage.php b/includes/OutputPage.php index 7c51381bf9..d60835e9d9 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -109,8 +109,7 @@ class OutputPage { * addStyle() and draws from the /skins folder. */ public function addExtensionStyle( $url ) { - $linkarr = array( 'rel' => 'stylesheet', 'href' => $url, 'type' => 'text/css' ); - array_push( $this->mExtStyles, $linkarr ); + array_push( $this->mExtStyles, $url ); } /** @@ -118,7 +117,7 @@ class OutputPage { * @param string $file filename in skins/common or complete on-server path (/foo/bar.js) */ function addScriptFile( $file ) { - global $wgStylePath, $wgStyleVersion, $wgJsMimeType, $wgScript, $wgUser; + global $wgStylePath, $wgStyleVersion, $wgScript, $wgUser; global $wgJSAutoloadClasses, $wgJSAutoloadLocalClasses, $wgEnableScriptLoader, $wgScriptPath; if( substr( $file, 0, 1 ) == '/' ) { @@ -163,15 +162,7 @@ class OutputPage { } // if the script loader did not find a way to add the script than add using addScript - $this->addScript( - Xml::element( 'script', - array( - 'type' => $wgJsMimeType, - 'src' => wfAppendQuery( $path, $this->getURIDparam() ), - ), - '', false - ) - ); + $this->addScript( Html::linkedScript( wfAppendQuery( $path, $this->getURIDparam() ) ) ); } /** @@ -180,7 +171,7 @@ class OutputPage { * different page load types (edit, upload, view, etc) */ function addCoreScripts2Top(){ - global $wgEnableScriptLoader, $wgStyleVersion, $wgJSAutoloadLocalClasses, $wgJsMimeType, $wgScriptPath, $wgEnableJS2system; + global $wgEnableScriptLoader, $wgStyleVersion, $wgJSAutoloadLocalClasses, $wgScriptPath, $wgEnableJS2system; //@@todo we should deprecate wikibits in favor of mv_embed and native jQuery functions if( $wgEnableJS2system ){ @@ -195,12 +186,7 @@ class OutputPage { $so = ''; foreach( $core_classes as $s ){ if( isset( $wgJSAutoloadLocalClasses[$s] ) ){ - $so.= Xml::element( 'script', array( - 'type' => $wgJsMimeType, - 'src' => "{$wgScriptPath}/{$wgJSAutoloadLocalClasses[$s]}?" . $this->getURIDparam() - ), - '', false - ); + $so .= Html::linkedScript( "{$wgScriptPath}/{$wgJSAutoloadLocalClasses[$s]}?" . $this->getURIDparam() ); } } $this->mScripts = $so . $this->mScripts; @@ -213,7 +199,7 @@ class OutputPage { */ function addScriptClass( $js_class ){ global $wgDebugJavaScript, $wgJSAutoloadLocalClasses, $wgJSAutoloadClasses, - $wgJsMimeType, $wgEnableScriptLoader, $wgStyleVersion, $wgScriptPath; + $wgEnableScriptLoader, $wgStyleVersion, $wgScriptPath; if( isset( $wgJSAutoloadClasses[$js_class] ) || isset( $wgJSAutoloadLocalClasses[$js_class] ) ){ if( $wgEnableScriptLoader ){ @@ -228,16 +214,8 @@ class OutputPage { }else if( isset( $wgJSAutoloadLocalClasses[$js_class] ) ){ $path.= $wgJSAutoloadLocalClasses[$js_class]; } - $urlApend = ( $wgDebugJavaScript) ? time() : $wgStyleVersion; - $this->addScript( - Xml::element( 'script', - array( - 'type' => $wgJsMimeType, - 'src' => "$path?" . $urlApend, - ), - '', false - ) - ); + $urlAppend = ( $wgDebugJavaScript ) ? time() : $wgStyleVersion; + $this->addScript( Html::linkedScript( "$path?$urlAppend" ) ); } return true; } @@ -250,7 +228,7 @@ class OutputPage { * @param $forcClassAry Boolean: false by default */ function getScriptLoaderJs( $forceClassAry = false ){ - global $wgJsMimeType, $wgStyleVersion, $wgRequest, $wgDebugJavaScript; + global $wgStyleVersion, $wgRequest, $wgDebugJavaScript; if( !$forceClassAry ){ $class_list = implode( ',', $this->mScriptLoaderClassList ); @@ -268,14 +246,7 @@ class OutputPage { //generate the unique request param (combine with the most recent revision id of any wiki page with the $wgStyleVersion var) - - return Xml::element( 'script', - array( - 'type' => $wgJsMimeType, - 'src' => wfScript( 'mwScriptLoader' ) . "?class={$class_list}{$debug_param}&".$this->getURIDparam(), - ), - '', false - ); + return Html::linkedScript( wfScript( 'mwScriptLoader' ) . "?class={$class_list}{$debug_param}&" . $this->getURIDparam() ); } function getURIDparam(){ @@ -304,8 +275,7 @@ class OutputPage { * @param string $script JavaScript text, no \n"; + $this->mScripts .= "\t\t" . Html::inlineScript( "\n\t\t$script\n\t\t" ) . "\n"; } function getScript() { @@ -1056,7 +1026,7 @@ class OutputPage { public function output() { global $wgUser, $wgOutputEncoding, $wgRequest; global $wgContLanguageCode, $wgDebugRedirects, $wgMimeType; - global $wgJsMimeType, $wgUseAjax, $wgAjaxWatch; + global $wgUseAjax, $wgAjaxWatch; global $wgEnableMWSuggest, $wgUniversalEditButton; global $wgArticle; @@ -1741,7 +1711,7 @@ class OutputPage { $this->getHeadItems(), )); if( $sk->usercss ){ - $ret .= ""; + $ret .= Html::inlineStyle( $sk->usercss ); } if( $wgEnableScriptLoader ) @@ -1922,7 +1892,7 @@ class OutputPage { * @param $style_css Mixed: inline CSS */ public function addInlineStyle( $style_css ){ - $this->mScripts .= ""; + $this->mScripts .= Html::inlineStyle( $style_css ); } /** @@ -1956,7 +1926,7 @@ class OutputPage { return ''; } } else { - $media = ''; + $media = null; } if( substr( $style, 0, 1 ) == '/' || @@ -1968,15 +1938,7 @@ class OutputPage { $url = $wgStylePath . '/' . $style . '?' . $wgStyleVersion; } - $attribs = array( - 'rel' => 'stylesheet', - 'href' => $url, - 'type' => 'text/css' ); - if( $media ) { - $attribs['media'] = $media; - } - - $link = Xml::element( 'link', $attribs ); + $link = Html::linkedStyle( $url, $media ); if( isset( $options['condition'] ) ) { $condition = htmlspecialchars( $options['condition'] ); diff --git a/includes/Skin.php b/includes/Skin.php index 0df32f0cab..38b6138cc9 100644 --- a/includes/Skin.php +++ b/includes/Skin.php @@ -333,15 +333,13 @@ class Skin extends Linker { } static function makeVariablesScript( $data ) { - global $wgJsMimeType; - - $r = array( "\n"; - return implode( "\n\t\t", $r ); + return Html::inlineScript( "\n\t\t" . implode( "\n\t\t", $r ) . + "\n\t\t" ); } /** @@ -632,8 +630,8 @@ END; ); // Add any extension CSS - foreach( $out->getExtStyle() as $tag ) { - $out->addStyle( $tag['href'] ); + foreach ( $out->getExtStyle() as $url ) { + $out->addStyle( $url ); } // If we use the site's dynamic CSS, throw that in, too @@ -978,8 +976,7 @@ END; * @return String HTML-wrapped JS code to be put before */ function bottomScripts() { - global $wgJsMimeType; - $bottomScriptText = "\n\t\t\n"; + $bottomScriptText = "\n\t\t" . Html::inlineScript( 'if (window.runOnloadHook) runOnloadHook();' ) . "\n"; wfRunHooks( 'SkinAfterBottomScripts', array( $this, &$bottomScriptText ) ); return $bottomScriptText; } diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php index b4ac3abf60..da7f0d3174 100644 --- a/includes/specials/SpecialSearch.php +++ b/includes/specials/SpecialSearch.php @@ -801,12 +801,10 @@ class SpecialSearch { } protected function searchFocus() { - global $wgJsMimeType; - return ""; + "});" ); } protected function formHeader( $term, $resultsShown, $totalNum ) { diff --git a/maintenance/parserTests.inc b/maintenance/parserTests.inc index 16307bafbc..c3d8680f52 100644 --- a/maintenance/parserTests.inc +++ b/maintenance/parserTests.inc @@ -647,6 +647,8 @@ class ParserTest { 'wgEnforceHtmlIds' => true, 'wgExternalLinkTarget' => false, 'wgAlwaysUseTidy' => false, + 'wgHtml5' => true, + 'wgWellFormedXml' => true, ); if ($config) {