MDL-33468 css_optimiser: Added support for @keyframes to the CSS optimiser

This commit is contained in:
Sam Hemelryk 2012-05-31 16:10:34 +12:00
parent f2e8d3798c
commit 1121f79b91
2 changed files with 222 additions and 58 deletions

View File

@ -526,6 +526,8 @@ class css_optimiser {
);
$imports = array();
$charset = false;
// Keyframes are used for CSS animation they will be processed right at the very end.
$keyframes = array();
$currentprocess = self::PROCESSING_START;
$currentrule = css_rule::init();
@ -579,6 +581,14 @@ class css_optimiser {
$currentmedia = $medias[$mediatypes];
$currentprocess = self::PROCESSING_SELECTORS;
$buffer = '';
} else if ($currentatrule == 'keyframes' && preg_match('#@((\-moz\-|\-webkit\-)?keyframes)\s*([^\s]+)#', $buffer, $matches)) {
$keyframefor = $matches[1];
$keyframename = $matches[3];
$keyframe = new css_keyframe($keyframefor, $keyframename);
$keyframes[] = $keyframe;
$currentmedia = $keyframe;
$currentprocess = self::PROCESSING_SELECTORS;
$buffer = '';
}
// continue 1: The switch processing chars
// continue 2: The switch processing the state
@ -612,8 +622,8 @@ class css_optimiser {
continue 3;
}
if (!empty($buffer)) {
if ($suspectatrule && preg_match('#@(media|import|charset)\s*#', $buffer, $matches)) {
$currentatrule = $matches[1];
if ($suspectatrule && preg_match('#@(media|import|charset|(\-moz\-|\-webkit\-)?(keyframes))\s*#', $buffer, $matches)) {
$currentatrule = (!empty($matches[3]))?$matches[3]:$matches[1];
$currentprocess = self::PROCESSING_ATRULE;
$buffer .= $char;
} else {
@ -655,6 +665,10 @@ class css_optimiser {
$currentmedia = $medias['all'];
$currentatrule = false;
$buffer = '';
} else if (strpos($currentatrule, 'keyframes') !== false) {
$currentmedia = $medias['all'];
$currentatrule = false;
$buffer = '';
}
// continue 1: The switch processing chars
// continue 2: The switch processing the state
@ -760,6 +774,18 @@ class css_optimiser {
}
$css .= $media->out();
}
if (count($keyframes) > 0) {
foreach ($keyframes as $keyframe) {
$this->optimisedrules += $keyframe->count_rules();
$this->optimisedselectors += $keyframe->count_selectors();
if ($keyframe->has_errors()) {
$this->errors += $keyframe->get_errors();
}
$css .= $keyframe->out();
}
}
$this->optimisedstrlen = strlen($css);
$this->timecomplete = microtime(true);
@ -1138,6 +1164,17 @@ abstract class css_writer {
return $output;
}
public static function keyframe($for, $name, array &$rules) {
$nl = self::get_separator();
$output = $nl."@{$for} {$name} {";
foreach ($rules as $rule) {
$output .= $rule->out();
}
$output .= '}';
return $output;
}
/**
* Returns CSS for a rule
*
@ -1536,44 +1573,23 @@ class css_rule {
}
}
return $css." has the following errors:\n".join("\n", $errors);
}
}
abstract class css_rule_collection {
/**
* A media class to organise rules by the media they apply to.
*
* @package core_css
* @category css
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class css_media {
/**
* An array of the different media types this instance applies to.
* @var array
*/
protected $types = array();
/**
* An array of rules within this media instance
* An array of rules within this collection instance
* @var array
*/
protected $rules = array();
/**
* Initalises a new media instance
*
* @param string $for The media that the contained rules are destined for.
* The collection must be able to print itself.
*/
public function __construct($for = 'all') {
$types = explode(',', $for);
$this->types = array_map('trim', $types);
}
abstract public function out();
/**
* Adds a new CSS rule to this media instance
* Adds a new CSS rule to this collection instance
*
* @param css_rule $newrule
*/
@ -1589,7 +1605,7 @@ class css_media {
}
/**
* Returns the rules used by this
* Returns the rules used by this collection
*
* @return array
*/
@ -1622,7 +1638,7 @@ class css_media {
}
/**
* Returns the total number of rules that exist within this media set
* Returns the total number of rules that exist within this collection
*
* @return int
*/
@ -1631,7 +1647,7 @@ class css_media {
}
/**
* Returns the total number of selectors that exist within this media set
* Returns the total number of selectors that exist within this collection
*
* @return int
*/
@ -1643,6 +1659,62 @@ class css_media {
return $count;
}
/**
* Returns true if the collection has any rules that have errors
*
* @return boolean
*/
public function has_errors() {
foreach ($this->rules as $rule) {
if ($rule->has_errors()) {
return true;
}
}
return false;
}
/**
* Returns any errors that have happened within rules in this collection.
*
* @return string
*/
public function get_errors() {
$errors = array();
foreach ($this->rules as $rule) {
if ($rule->has_errors()) {
$errors[] = $rule->get_error_string();
}
}
return $errors;
}
}
/**
* A media class to organise rules by the media they apply to.
*
* @package core_css
* @category css
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class css_media extends css_rule_collection {
/**
* An array of the different media types this instance applies to.
* @var array
*/
protected $types = array();
/**
* Initalises a new media instance
*
* @param string $for The media that the contained rules are destined for.
*/
public function __construct($for = 'all') {
$types = explode(',', $for);
$this->types = array_map('trim', $types);
}
/**
* Returns the CSS for this media and all of its rules.
*
@ -1660,34 +1732,56 @@ class css_media {
public function get_types() {
return $this->types;
}
}
/**
* Returns true if the media has any rules that have errors
* A media class to organise rules by the media they apply to.
*
* @return boolean
* @package core_css
* @category css
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
public function has_errors() {
foreach ($this->rules as $rule) {
if ($rule->has_errors()) {
return true;
}
}
return false;
}
class css_keyframe extends css_rule_collection {
/** @var string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes */
protected $for;
/** @var string $name The name for the keyframes */
protected $name;
/**
* Returns any errors that have happened within rules in this media set.
* Constructs a new keyframe
*
* @param string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes
* @param string $name The name for the keyframes
*/
public function __construct($for, $name) {
$this->for = $for;
$this->name = $name;
}
/**
* Returns the directive of this keyframe
*
* e.g. keyframes, -moz-keyframes, -webkit-keyframes
* @return string
*/
public function get_for() {
return $this->for;
}
/**
* Returns the name of this keyframe
* @return string
*/
public function get_name() {
return $this->name;
}
/**
* Returns the CSS for this collection of keyframes and all of its rules.
*
* @return string
*/
public function get_errors() {
$errors = array();
foreach ($this->rules as $rule) {
if ($rule->has_errors()) {
$errors[] = $rule->get_error_string();
}
}
return $errors;
public function out() {
return css_writer::keyframe($this->for, $this->name, $this->rules);
}
}

View File

@ -72,7 +72,7 @@ class css_optimiser_testcase extends advanced_testcase {
$this->try_invalid_css_handling($optimiser);
$this->try_bulk_processing($optimiser);
$this->try_break_things($optimiser);
$this->try_advanced_css_animation($optimiser);
$this->try_keyframe_css_animation($optimiser);
}
/**
@ -807,17 +807,87 @@ CSS;
$this->assertEquals($cssout, $optimiser->process($cssin));
}
public function try_advanced_css_animation(css_optimiser $optimiser) {
$css = '.dndupload-arrow{width:56px;height:47px;position:absolute;animation:mymove 5s infinite;-moz-animation:mymove 5s infinite;-webkit-animation:mymove 5s infinite;background:url(\'[[pix:theme|fp/dnd_arrow]]\') center no-repeat;margin-left:-28px;}';
public function try_keyframe_css_animation(css_optimiser $optimiser) {
$css = '.dndupload-arrow{width:56px;height:47px;position:absolute;animation:mymove 5s infinite;-moz-animation:mymove 5s infinite;-webkit-animation:mymove 5s infinite;background:url(\'[[pix:theme|fp/dnd_arrow]]\') no-repeat center;margin-left:-28px;}';
$this->assertEquals($css, $optimiser->process($css));
$css = '@keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}';
$this->assertEquals($css, $optimiser->process($css));
$css = "@keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}\n";
$css .= "@-moz-keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}\n";
$css .= "@-webkit-keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}";
$this->assertEquals($css, $optimiser->process($css));
$cssin = <<<CSS
.test {color:#FFF;}
.testtwo {color:#FFF;}
@media print {
.test {background-color:#FFF;}
}
.dndupload-arrow{width:56px;height:47px;position:absolute;animation:mymove 5s infinite;-moz-animation:mymove 5s infinite;-webkit-animation:mymove 5s infinite;background:url('[[pix:theme|fp/dnd_arrow]]') no-repeat center;margin-left:-28px;}
@media print {
.test {background-color:#000;}
}
@keyframes mymove {0%{top:10px;} 12%{top:40px;} 30%{top:20px} 65%{top:35px;} 100%{top:9px;}}
@-moz-keyframes mymove{0%{top:10px;} 12%{top:40px;} 30%{top:20px} 65%{top:35px;} 100%{top:9px;}}
@-webkit-keyframes mymove {0%{top:10px;} 12%{top:40px;} 30%{top:20px} 65%{top:35px;} 100%{top:9px;}}
@media print {
.test {background-color:#333;}
}
.test {color:#888;}
.testtwo {color:#888;}
CSS;
$cssout = <<<CSS
.test,
.testtwo{color:#888;}
.dndupload-arrow{width:56px;height:47px;position:absolute;animation:mymove 5s infinite;-moz-animation:mymove 5s infinite;-webkit-animation:mymove 5s infinite;background:url('[[pix:theme|fp/dnd_arrow]]') no-repeat center;margin-left:-28px;}
@media print {
.test{background:#333;}
}
@keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}
@-moz-keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}
@-webkit-keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}
CSS;
$this->assertEquals($cssout, $optimiser->process($cssin));
$cssin = '@keyframes mymove{0%{top:10px;}12%{top:40px;}30%{top:20px}65%{top:35px;}100%{top:9px;}}';
$cssout = '@keyframes mymove{0%{top:10px;} 12%{top:40px;} 30%{top:20px} 65%{top:35px;} 100%{top:9px;}}';
$cssin = <<<CSS
.dndupload-target {display:none;}
.dndsupported .dndupload-ready .dndupload-target {display:block;}
.dndupload-uploadinprogress {display:none;text-align:center;}
.dndupload-uploading .dndupload-uploadinprogress {display:block;}
.dndupload-arrow {background:url('[[pix:theme|fp/dnd_arrow]]') center no-repeat;width:56px;height:47px;position:absolute;margin-left: -28px;/*right:46%;left:46%;*/animation:mymove 5s infinite;-moz-animation:mymove 5s infinite;-webkit-animation:mymove 5s infinite;}
@keyframes mymove {0%{top:10px;} 12%{top:40px;} 30%{top:20px} 65%{top:35px;} 100%{top:9px;}}@-moz-keyframes mymove{0%{top:10px;} 12%{top:40px;} 30%{top:20px} 65%{top:35px;} 100%{top:9px;}}@-webkit-keyframes mymove {0%{top:10px;} 12%{top:40px;} 30%{top:20px} 65%{top:35px;} 100%{top:9px;}}
/*
* Select Dialogue (File Manager only)
*/
.filemanager.fp-select .fp-select-loading {display:none;}
.filemanager.fp-select.loading .fp-select-loading {display:block;}
.filemanager.fp-select.loading form {display:none;}
CSS;
$cssout = <<<CSS
.dndupload-target,
.filemanager.fp-select .fp-select-loading,
.filemanager.fp-select.loading form{display:none;}
.dndsupported .dndupload-ready .dndupload-target,
.dndupload-uploading .dndupload-uploadinprogress,
.filemanager.fp-select.loading .fp-select-loading{display:block;}
.dndupload-uploadinprogress{display:none;text-align:center;}
.dndupload-arrow{width:56px;height:47px;position:absolute;animation:mymove 5s infinite;-moz-animation:mymove 5s infinite;-webkit-animation:mymove 5s infinite;background:url('[[pix:theme|fp/dnd_arrow]]') no-repeat center;margin-left:-28px;}
@keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}
@-moz-keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}
@-webkit-keyframes mymove {0%{top:10px;}12%{top:40px;}30%{top:20px;}65%{top:35px;}100%{top:9px;}}
CSS;
$this->assertEquals($cssout, $optimiser->process($cssin));
$cssin = '@keyframes mymove{0%{top:10px;} 12%{top:40px;} 30%{top:20px} 65%{top:35px;} 100%{top:9px;}} @-moz-keyframes mymove{0%{top:10px;}12%{top:40px;}30%{top:20px}65%{top:35px;}100%{top:9px;}} @-webkit-keyframes mymove{0%{top:10px;}12%{top:40px;}30%{top:20px}65%{top:35px;}100%{top:9px;}}';
$cssout = '@keyframes mymove{0%{top:10px;} 12%{top:40px;} 30%{top:20px} 65%{top:35px;} 100%{top:9px;}} @-moz-keyframes mymove{0%{top:10px;}12%{top:40px;}30%{top:20px}65%{top:35px;}100%{top:9px;}} @-webkit-keyframes mymove{0%{top:10px;}12%{top:40px;}30%{top:20px}65%{top:35px;}100%{top:9px;}}';
$this->assertEquals($cssout, $optimiser->process($cssin));
}
}