MDL-33365 core: Move JS output to body

It turns out that, after all these years, we've been doing it wrong.
The YUI Loader should be loaded at the top of the body, not in the head.

Having it in the head means that the body has not yet loaded, and we see
a number of minor issues for scripts which happen very early in the load
process. One of these is the creation of invalid HTML from the YUI loader
because it is not able to insert in the body as it has not yet been
created.

To be safe, we must move all JS to the same point (because of unknown
dependencies between them). These should be placed as early in the body as
possible.

We also change the way in which the legacy YUI css modules are loaded.
This commit is contained in:
Andrew Nicols 2015-07-24 09:09:18 +08:00
parent dda862abb5
commit 2300a312a4

View File

@ -148,6 +148,11 @@ class page_requirements_manager {
*/
protected $YUI_config;
/**
* @var array $yuicssmodules
*/
protected $yuicssmodules = array();
/**
* @var array Some config vars exposed in JS, please no secret stuff there
*/
@ -374,6 +379,9 @@ class page_requirements_manager {
'moodle');
$page->requires->yui_module('moodle-core-blocks', 'M.core_blocks.init_dragdrop', array($params), null, true);
}
// Include the YUI CSS Modules.
$page->requires->set_yuicssmodules($page->theme->yuicssmodules);
}
/**
@ -1047,6 +1055,15 @@ class page_requirements_manager {
$this->jsinitcode[] = $jscode;
}
/**
* Set the CSS Modules to be included from YUI.
*
* @param array $modules The list of YUI CSS Modules to include.
*/
public function set_yuicssmodules(array $modules = array()) {
$this->yuicssmodules = $modules;
}
/**
* Ensure that the specified JavaScript function is called from an inline script
* from page footer.
@ -1320,19 +1337,53 @@ class page_requirements_manager {
}
/**
* Returns basic YUI3 JS loading code.
* YUI3 is using autoloading of both CSS and JS code.
* Returns basic YUI3 CSS code.
*
* Major benefit of this compared to standard js/csss loader is much improved
* caching, better browser cache utilisation, much fewer http requests.
*
* @param moodle_page $page
* @return string
*/
protected function get_yui3lib_headcode($page) {
protected function get_yui3lib_headcss() {
global $CFG;
$yuiformat = '-min';
if ($this->yui3loader->filter === 'RAW') {
$yuiformat = '';
}
$code = '';
if ($this->yui3loader->combine) {
if (!empty($this->yuicssmodules)) {
$modules = array();
foreach ($this->yuicssmodules as $module) {
$modules[] = "$CFG->yui3version/$module/$module-min.css";
}
$code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->comboBase.implode('&amp;', $modules).'" />';
}
$code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->local_comboBase.'rollup/'.$CFG->yui3version.'/yui-moodlesimple' . $yuiformat . '.css" />';
} else {
if (!empty($this->yuicssmodules)) {
foreach ($this->yuicssmodules as $module) {
$code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->base.$module.'/'.$module.'-min.css" />';
}
}
$code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->local_comboBase.'rollup/'.$CFG->yui3version.'/yui-moodlesimple' . $yuiformat . '.css" />';
}
if ($this->yui3loader->filter === 'RAW') {
$code = str_replace('-min.css', '.css', $code);
} else if ($this->yui3loader->filter === 'DEBUG') {
$code = str_replace('-min.css', '.css', $code);
}
return $code;
}
/**
* Returns basic YUI3 JS loading code.
*
* @return string
*/
protected function get_yui3lib_headcode() {
global $CFG;
$jsrev = $this->get_jsrev();
@ -1357,35 +1408,18 @@ class page_requirements_manager {
);
if ($this->yui3loader->combine) {
if (!empty($page->theme->yuicssmodules)) {
$modules = array();
foreach ($page->theme->yuicssmodules as $module) {
$modules[] = "$CFG->yui3version/$module/$module-min.css";
}
$code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->comboBase.implode('&amp;', $modules).'" />';
}
$code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->local_comboBase.'rollup/'.$CFG->yui3version.'/yui-moodlesimple' . $yuiformat . '.css" />';
$code .= '<script type="text/javascript" src="'.$this->yui3loader->local_comboBase
. implode('&amp;', $baserollups) . '"></script>';
return '<script type="text/javascript" src="' .
$this->yui3loader->local_comboBase .
implode('&amp;', $baserollups) .
'"></script>';
} else {
if (!empty($page->theme->yuicssmodules)) {
foreach ($page->theme->yuicssmodules as $module) {
$code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->base.$module.'/'.$module.'-min.css" />';
}
}
$code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->local_comboBase.'rollup/'.$CFG->yui3version.'/yui-moodlesimple' . $yuiformat . '.css" />';
$code = '';
foreach ($baserollups as $rollup) {
$code .= '<script type="text/javascript" src="'.$this->yui3loader->local_comboBase.$rollup.'"></script>';
}
return $code;
}
if ($this->yui3loader->filter === 'RAW') {
$code = str_replace('-min.css', '.css', $code);
} else if ($this->yui3loader->filter === 'DEBUG') {
$code = str_replace('-min.css', '.css', $code);
}
return $code;
}
/**
@ -1400,12 +1434,15 @@ class page_requirements_manager {
// It is suitable only for things like mod/data which accepts CSS from teachers.
$attributes = array('rel'=>'stylesheet', 'type'=>'text/css');
// Add the YUI code first. We want this to be overridden by any Moodle CSS.
$code = $this->get_yui3lib_headcss();
// This line of code may look funny but it is currently required in order
// to avoid MASSIVE display issues in Internet Explorer.
// As of IE8 + YUI3.1.1 the reference stylesheet (firstthemesheet) gets
// ignored whenever another resource is added until such time as a redraw
// is forced, usually by moving the mouse over the affected element.
$code = html_writer::tag('script', '/** Required in order to fix style inclusion problems in IE with YUI **/', array('id'=>'firstthemesheet', 'type'=>'text/css'));
$code .= html_writer::tag('script', '/** Required in order to fix style inclusion problems in IE with YUI **/', array('id'=>'firstthemesheet', 'type'=>'text/css'));
$urls = $this->cssthemeurls + $this->cssurls;
foreach ($urls as $url) {
@ -1449,6 +1486,9 @@ class page_requirements_manager {
$output = '';
// Add all standard CSS for this page.
$output .= $this->get_css_code();
// Set up the M namespace.
$js = "var M = {}; M.yui = {};\n";
@ -1470,19 +1510,6 @@ class page_requirements_manager {
$output .= html_writer::script($js);
// YUI3 JS and CSS need to be loaded in the header but after the YUI_config has been created.
// They should be cached well by the browser.
$output .= $this->get_yui3lib_headcode($page);
// Add hacked jQuery support, it is not intended for standard Moodle distribution!
$output .= $this->get_jquery_headcode();
// Now theme CSS + custom CSS in this specific order.
$output .= $this->get_css_code();
// Link our main JS file, all core stuff should be there.
$output .= html_writer::script('', $this->js_fix_url('/lib/javascript-static.js'));
// Add variables.
if ($this->jsinitvariables['head']) {
$js = '';
@ -1493,13 +1520,6 @@ class page_requirements_manager {
$output .= html_writer::script($js);
}
// All the other linked things from HEAD - there should be as few as possible.
if ($this->jsincludes['head']) {
foreach ($this->jsincludes['head'] as $url) {
$output .= html_writer::script('', $url);
}
}
// Mark head sending done, it is not possible to anything there.
$this->headdone = true;
@ -1524,6 +1544,22 @@ class page_requirements_manager {
}
$output = html_writer::tag('div', $links, array('class'=>'skiplinks')) . "\n";
// YUI3 JS needs to be loaded early in the body. It should be cached well by the browser.
$output .= $this->get_yui3lib_headcode();
// Add hacked jQuery support, it is not intended for standard Moodle distribution!
$output .= $this->get_jquery_headcode();
// Link our main JS file, all core stuff should be there.
$output .= html_writer::script('', $this->js_fix_url('/lib/javascript-static.js'));
// All the other linked things from HEAD - there should be as few as possible.
if ($this->jsincludes['head']) {
foreach ($this->jsincludes['head'] as $url) {
$output .= html_writer::script('', $url);
}
}
// Then the clever trick for hiding of things not needed when JS works.
$output .= html_writer::script("document.body.className += ' jsenabled';") . "\n";
$this->topofbodydone = true;