feat(html/minifier): Improve minification of attributes (#4625)

This commit is contained in:
Alexander Akait 2022-05-12 07:40:47 +03:00 committed by GitHub
parent 5a53bb771d
commit 5679b69600
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 334 additions and 38 deletions

View File

@ -51,6 +51,8 @@ static BOOLEAN_ATTRIBUTES: &[&str] = &[
static EVENT_HANDLER_ATTRIBUTES: &[&str] = &[
"onabort",
"onautocomplete",
"onautocompleteerror",
"onauxclick",
"onbeforematch",
"oncancel",
@ -80,9 +82,7 @@ static EVENT_HANDLER_ATTRIBUTES: &[&str] = &[
"onkeydown",
"onkeypress",
"onkeyup",
"onloadeddata",
"onloadedmetadata",
"onloadstart",
"onmousewheel",
"onmousedown",
"onmouseenter",
"onmouseleave",
@ -117,6 +117,9 @@ static EVENT_HANDLER_ATTRIBUTES: &[&str] = &[
"onerror",
"onfocus",
"onload",
"onloadeddata",
"onloadedmetadata",
"onloadstart",
"onresize",
"onscroll",
"onafterprint",
@ -140,12 +143,11 @@ static EVENT_HANDLER_ATTRIBUTES: &[&str] = &[
"onpaste",
"onreadystatechange",
"onvisibilitychange",
"onshow",
"onsort",
];
// TODO improve list - event handlers + remove multiple whitespace from class +
// test for custom elements
static ALLOW_TO_TRIM_ATTRIBUTES: &[&str] = &[
"id",
"class",
"style",
"tabindex",
@ -158,7 +160,20 @@ static ALLOW_TO_TRIM_ATTRIBUTES: &[&str] = &[
"colspan",
];
struct Minifier {}
static COMMA_SEPARATED_ATTRIBUTES: &[&str] = &["srcset", "sizes"];
static SPACE_SEPARATED_ATTRIBUTES: &[&str] = &[
"class",
"rel",
"aria-describedby",
"aria-labelledby",
"aria-owns",
"autocomplete",
];
struct Minifier {
current_element_namespace: Option<Namespace>,
}
impl Minifier {
fn is_boolean_attribute(&self, name: &str) -> bool {
@ -169,8 +184,12 @@ impl Minifier {
EVENT_HANDLER_ATTRIBUTES.contains(&name)
}
fn allow_to_trim(&self, name: &str) -> bool {
ALLOW_TO_TRIM_ATTRIBUTES.contains(&name)
fn is_comma_separated_attribute(&self, name: &str) -> bool {
COMMA_SEPARATED_ATTRIBUTES.contains(&name)
}
fn is_space_separated_attribute(&self, name: &str) -> bool {
SPACE_SEPARATED_ATTRIBUTES.contains(&name)
}
fn is_default_attribute_value(
@ -215,6 +234,12 @@ impl Minifier {
)
| (Namespace::HTML, "form", "method", "get")
| (Namespace::HTML, "form", "target", "_self")
| (
Namespace::HTML,
"form",
"enctype",
"application/x-www-form-urlencoded"
)
| (Namespace::HTML, "input", "type", "text")
| (Namespace::HTML, "input", "size", "20")
| (Namespace::HTML, "track", "kind", "subtitles")
@ -263,6 +288,12 @@ impl Minifier {
| (Namespace::HTML, "base", "target", "_self")
| (Namespace::HTML, "canvas", "height", "150")
| (Namespace::HTML, "canvas", "width", "300")
| (Namespace::HTML, "col", "span", "1")
| (Namespace::HTML, "colgroup", "span", "1")
| (Namespace::HTML, "td", "colspan", "1")
| (Namespace::HTML, "td", "rowspan", "1")
| (Namespace::HTML, "th", "colspan", "1")
| (Namespace::HTML, "th", "rowspan", "1")
)
}
@ -275,12 +306,22 @@ impl Minifier {
false
}
fn allow_to_trim(&self, name: &str) -> bool {
ALLOW_TO_TRIM_ATTRIBUTES.contains(&name) || EVENT_HANDLER_ATTRIBUTES.contains(&name)
}
}
impl VisitMut for Minifier {
fn visit_mut_element(&mut self, n: &mut Element) {
let old_current_element_namespace = self.current_element_namespace.take();
self.current_element_namespace = Some(n.namespace);
n.visit_mut_children_with(self);
self.current_element_namespace = old_current_element_namespace;
n.children.retain(|child| !matches!(child, Child::Comment(comment) if !self.is_conditional_comment(&comment.data)));
let mut already_seen: AHashSet<JsWord> = Default::default();
@ -296,17 +337,22 @@ impl VisitMut for Minifier {
return true;
}
let is_empty_value = (&*attribute.value.as_ref().unwrap()).trim().is_empty();
if self.is_default_attribute_value(
n.namespace,
&n.tag_name,
&attribute.name,
attribute.value.as_ref().unwrap(),
) || (matches!(&*attribute.name, "id" | "class" | "style")
&& (&*attribute.value.as_ref().unwrap()).trim().is_empty())
) || (matches!(&*attribute.name, "id" | "class" | "style") && is_empty_value)
{
return false;
}
if self.is_event_handler_attribute(&attribute.name) && is_empty_value {
return false;
}
true
});
}
@ -318,32 +364,59 @@ impl VisitMut for Minifier {
return;
}
let value = n.value.as_ref().unwrap();
let is_element_html_namespace = self.current_element_namespace == Some(Namespace::HTML);
match &n.name {
name if self.is_boolean_attribute(name) => {
n.value = None;
}
name if self.is_event_handler_attribute(name)
&& value.to_lowercase().starts_with("javascript:") =>
{
let new_value: String = value.as_ref().chars().skip(11).collect();
n.value = Some(new_value.into());
}
_ => {}
}
if self.is_boolean_attribute(&n.name) {
if is_element_html_namespace && self.is_boolean_attribute(&n.name) {
n.value = None;
return;
}
let mut value: &str = &*(n.value.as_ref().unwrap().clone());
let mut value = match &n.value {
Some(value) => value.to_string(),
_ => {
unreachable!();
}
};
if is_element_html_namespace {
if &n.name == "contenteditable" && value == "true" {
n.value = Some("".into());
return;
}
if self.is_comma_separated_attribute(&n.name) {
let values = value.split(',');
let mut new_values = vec![];
for value in values {
new_values.push(value.trim());
}
n.value = Some(new_values.join(",").into());
return;
}
if self.is_space_separated_attribute(&n.name) {
value = value.split_whitespace().collect::<Vec<_>>().join(" ");
n.value = Some(value.into());
return;
}
}
if self.allow_to_trim(&n.name) {
value = value.trim();
value = value.trim().to_string();
}
if self.is_event_handler_attribute(&n.name)
&& value.to_lowercase().starts_with("javascript:")
{
value = value.chars().skip(11).collect();
}
n.value = Some(value.into());
@ -351,5 +424,7 @@ impl VisitMut for Minifier {
}
pub fn minify(document: &mut Document) {
document.visit_mut_with(&mut Minifier {});
document.visit_mut_with(&mut Minifier {
current_element_namespace: None,
});
}

View File

@ -0,0 +1,9 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<a href="13.html" id="rm13" aria-labelledby=" rm13 attr ">read more</a>
</body>
</html>

View File

@ -0,0 +1,7 @@
<!doctype html><html lang=en><head>
<title>Document</title>
</head>
<body>
<a href=13.html id=rm13 aria-labelledby="rm13 attr">read more</a>
</body></html>

View File

@ -21,5 +21,24 @@
class1 class-23 ">foo bar baz</p>
<p class=" foo bar
qqq">foo bar baz</p>
<p class="
foo
bar
">foo bar baz</p>
</body>
</html>

View File

@ -5,11 +5,8 @@
<p class="foo bar">foo bar baz</p>
<p class=foo>foo bar baz</p>
<p class=foo>foo bar baz</p>
<p class="foo
class1 class-23">foo bar baz</p>
<p class="foo class1 class-23">foo bar baz</p>
<p class="foo bar qqq">foo bar baz</p>
<p class="foo bar">foo bar baz</p>
</body></html>

View File

@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<table width="200" border="1" align="center" cellpadding="4" cellspacing="0">
<tr>
<th colspan="1" rowspan="1" scope="col">Cell 1</th>
</tr>
<tr>
<td class="test" colspan="1" rowspan="1" bgcolor="#FBF0DB">
Cell 1
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,16 @@
<!doctype html><html lang=en><head>
<title>Document</title>
</head>
<body>
<table width=200 border=1 align=center cellpadding=4 cellspacing=0>
<tbody><tr>
<th scope=col>Cell 1</th>
</tr>
<tr>
<td class=test bgcolor=#FBF0DB>
Cell 1
</td>
</tr>
</tbody></table>
</body></html>

View File

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div contenteditable="true">Stress reliever</div>
<div contenteditable="">Stress reliever</div>
<div contenteditable="false">Stress reliever</div>
</body>
</html>

View File

@ -0,0 +1,9 @@
<!doctype html><html lang=en><head>
<title>Document</title>
</head>
<body>
<div contenteditable="">Stress reliever</div>
<div contenteditable="">Stress reliever</div>
<div contenteditable=false>Stress reliever</div>
</body></html>

View File

@ -3,7 +3,6 @@
</head>
<body>
<p title=\n lang="" dir="">x</p>
<p onclick="" ondblclick=" " onmousedown="" onmouseup="" onmouseover=" " onmousemove="" onmouseout="" onkeypress="
" onkeydown="" onkeyup="">x</p>
<p>x</p>
</body></html>

View File

@ -8,5 +8,12 @@
<input type="text" onkeydown="javascript:myFunction()">
</form>
<div type="text" onmouseover="javascript:myFunction()">test</div>
<div type="text" onmouseover=" javascript:myFunction() ">test</div>
<div type="text" onmouseover="">test</div>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice"
style="width:100%; height:100%; position:absolute; top:0; left:0; z-index:-1;">
<circle onmouseover=" javascript:alert('test') " cx="50" cy="50" r="30" style="fill:url(#gradient)" />
</svg>
</body>
</html>

View File

@ -6,5 +6,10 @@
<input onkeydown=myFunction()>
</form>
<div type=text onmouseover=myFunction()>test</div>
<div type=text onmouseover=myFunction()>test</div>
<div type=text>test</div>
<svg xmlns=http://www.w3.org/2000/svg version=1.1 viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice" style="width:100%; height:100%; position:absolute; top:0; left:0; z-index:-1;">
<circle onmouseover="alert('test')" cx=50 cy=50 r=30 style=fill:url(#gradient)></circle>
</svg>
</body></html>

View File

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<a href="#" rel="value1 nofollow">Link</a>
<a href="#" rel=" value1 nofollow ">Link</a>
<a href="#" rel=" nofollow ">Link</a>
</body>
</html>

View File

@ -0,0 +1,9 @@
<!doctype html><html lang=en><head>
<title>Document</title>
</head>
<body>
<a href=# rel="value1 nofollow">Link</a>
<a href=# rel="value1 nofollow">Link</a>
<a href=# rel=nofollow>Link</a>
</body></html>

View File

@ -0,0 +1,25 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<img srcset="elva-fairy-480w.jpg 480w,
elva-fairy-800w.jpg 800w"
sizes="(max-width: 600px) 480px,
800px"
src="elva-fairy-800w.jpg"
alt="Elva dressed as a fairy">
<img srcset="elva-fairy-320w.jpg,
elva-fairy-480w.jpg 1.5x,
elva-fairy-640w.jpg 2x"
src="elva-fairy-640w.jpg"
alt="Elva dressed as a fairy">
<img src="favicon72.png"
alt="MDN logo"
srcset=" favicon144.png 2x ">
</body>
</html>

View File

@ -0,0 +1,12 @@
<!doctype html><html lang=en><head>
<title>Document</title>
</head>
<body>
<img srcset="elva-fairy-480w.jpg 480w,elva-fairy-800w.jpg 800w" sizes="(max-width: 600px) 480px,800px" src=elva-fairy-800w.jpg alt="Elva dressed as a fairy">
<img srcset="elva-fairy-320w.jpg,elva-fairy-480w.jpg 1.5x,elva-fairy-640w.jpg 2x" src=elva-fairy-640w.jpg alt="Elva dressed as a fairy">
<img src=favicon72.png alt="MDN logo" srcset="favicon144.png 2x">
</body></html>

View File

@ -0,0 +1,36 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<form action="" method="get" class="form-example" enctype="application/x-www-form-urlencoded">
<div class="form-example">
<label for="name">Enter your name: </label>
<input type="text" name="name" id="name" required>
</div>
<div class="form-example">
<label for="email">Enter your email: </label>
<input type="email" name="email" id="email" required>
</div>
<div class="form-example">
<input type="submit" value="Subscribe!">
</div>
</form>
<form action="#" accept-charset="US-ASCII UTF-8">
First name:
<input type="text" name="fname">
<br> Last name:
<input type="text" name="lname">
<br>
<input type="submit" value="Submit">
<input name="cc-number" id="cc-number" autocomplete="off">
<input name="cc-number1" id="cc-number1" autocomplete="
organization
organization-title
">
</form>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!doctype html><html lang=en><head>
<title>Document</title>
</head>
<body>
<form action="" class=form-example>
<div class=form-example>
<label for=name>Enter your name: </label>
<input name=name id=name required>
</div>
<div class=form-example>
<label for=email>Enter your email: </label>
<input type=email name=email id=email required>
</div>
<div class=form-example>
<input type=submit value=Subscribe!>
</div>
</form>
<form action=# accept-charset="US-ASCII UTF-8">
First name:
<input name=fname>
<br> Last name:
<input name=lname>
<br>
<input type=submit value=Submit>
<input name=cc-number id=cc-number autocomplete=off>
<input name=cc-number1 id=cc-number1 autocomplete="organization organization-title">
</form>
</body></html>