Skip to content

Commit

Permalink
feat(html/minifier): Handle crossorigin (#6261)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Oct 27, 2022
1 parent ab4fd93 commit 782da5c
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 49 deletions.
2 changes: 2 additions & 0 deletions crates/swc_atoms/words.txt
Expand Up @@ -612,6 +612,7 @@ animatemotion
animatetransform
animation
animation-name
anonymous
annotation
annotation-xml
any
Expand Down Expand Up @@ -725,6 +726,7 @@ continue
counter-style
createClass
createReactClass
crossorigin
cubic-bezier
custom-media
cursor
Expand Down
136 changes: 89 additions & 47 deletions crates/swc_html_minifier/src/lib.rs
Expand Up @@ -367,21 +367,25 @@ fn get_white_space(namespace: Namespace, tag_name: &JsWord) -> WhiteSpace {
}

impl Minifier<'_> {
fn is_event_handler_attribute(&self, name: &JsWord) -> bool {
EVENT_HANDLER_ATTRIBUTES.contains(&&**name)
fn is_event_handler_attribute(&self, attribute: &Attribute) -> bool {
EVENT_HANDLER_ATTRIBUTES.contains(&&*attribute.name)
}

fn is_boolean_attribute(&self, element: &Element, name: &JsWord) -> bool {
fn is_boolean_attribute(&self, element: &Element, attribute: &Attribute) -> bool {
if element.namespace != Namespace::HTML {
return false;
}

if let Some(global_pseudo_element) = HTML_ELEMENTS_AND_ATTRIBUTES.get(&js_word!("*")) {
if let Some(element) = global_pseudo_element.other.get(name) {
if let Some(element) = global_pseudo_element.other.get(&attribute.name) {
if element.boolean.is_some() && element.boolean.unwrap() {
return true;
}
}
}

if let Some(element) = HTML_ELEMENTS_AND_ATTRIBUTES.get(&element.tag_name) {
if let Some(element) = element.other.get(name) {
if let Some(element) = element.other.get(&attribute.name) {
if element.boolean.is_some() && element.boolean.unwrap() {
return true;
}
Expand All @@ -391,25 +395,25 @@ impl Minifier<'_> {
false
}

fn is_trimable_separated_attribute(&self, element: &Element, attribute_name: &JsWord) -> bool {
if ALLOW_TO_TRIM_GLOBAL_ATTRIBUTES.contains(&&**attribute_name) {
fn is_trimable_separated_attribute(&self, element: &Element, attribute: &Attribute) -> bool {
if ALLOW_TO_TRIM_GLOBAL_ATTRIBUTES.contains(&&*attribute.name) {
return true;
}

match element.namespace {
Namespace::HTML => {
ALLOW_TO_TRIM_HTML_ATTRIBUTES.contains(&(&element.tag_name, attribute_name))
ALLOW_TO_TRIM_HTML_ATTRIBUTES.contains(&(&element.tag_name, &*attribute.name))
}
Namespace::SVG => {
ALLOW_TO_TRIM_SVG_ATTRIBUTES.contains(&(&element.tag_name, attribute_name))
ALLOW_TO_TRIM_SVG_ATTRIBUTES.contains(&(&element.tag_name, &*attribute.name))
}
_ => false,
}
}

fn is_comma_separated_attribute(&self, element: &Element, attribute_name: &JsWord) -> bool {
fn is_comma_separated_attribute(&self, element: &Element, attribute: &Attribute) -> bool {
match element.namespace {
Namespace::HTML => match *attribute_name {
Namespace::HTML => match attribute.name {
js_word!("content")
if element.tag_name == js_word!("meta")
&& (self.element_has_attribute_with_value(
Expand Down Expand Up @@ -450,52 +454,54 @@ impl Minifier<'_> {
{
true
}
_ if attribute_name == "exportparts" => true,
_ => COMMA_SEPARATED_HTML_ATTRIBUTES.contains(&(&element.tag_name, attribute_name)),
_ if attribute.name == js_word!("exportparts") => true,
_ => {
COMMA_SEPARATED_HTML_ATTRIBUTES.contains(&(&element.tag_name, &*attribute.name))
}
},
Namespace::SVG => {
COMMA_SEPARATED_SVG_ATTRIBUTES.contains(&(&element.tag_name, attribute_name))
COMMA_SEPARATED_SVG_ATTRIBUTES.contains(&(&element.tag_name, &*attribute.name))
}
_ => false,
}
}

fn is_space_separated_attribute(&self, element: &Element, attribute_name: &JsWord) -> bool {
if SPACE_SEPARATED_GLOBAL_ATTRIBUTES.contains(&&**attribute_name) {
fn is_space_separated_attribute(&self, element: &Element, attribute: &Attribute) -> bool {
if SPACE_SEPARATED_GLOBAL_ATTRIBUTES.contains(&&*attribute.name) {
return true;
}

match element.namespace {
Namespace::HTML => {
SPACE_SEPARATED_HTML_ATTRIBUTES.contains(&(&element.tag_name, attribute_name))
SPACE_SEPARATED_HTML_ATTRIBUTES.contains(&(&element.tag_name, &*attribute.name))
}
Namespace::SVG => {
match *attribute_name {
match attribute.name {
js_word!("transform")
| js_word!("stroke-dasharray")
| js_word!("clip-path")
| js_word!("requiredFeatures") => return true,
_ => {}
}

SPACE_SEPARATED_SVG_ATTRIBUTES.contains(&(&element.tag_name, attribute_name))
SPACE_SEPARATED_SVG_ATTRIBUTES.contains(&(&element.tag_name, &*attribute.name))
}
_ => false,
}
}

fn is_semicolon_separated_attribute(&self, element: &Element, attribute_name: &JsWord) -> bool {
fn is_semicolon_separated_attribute(&self, element: &Element, attribute: &Attribute) -> bool {
match element.namespace {
Namespace::SVG => {
SEMICOLON_SEPARATED_SVG_ATTRIBUTES.contains(&(&element.tag_name, attribute_name))
SEMICOLON_SEPARATED_SVG_ATTRIBUTES.contains(&(&element.tag_name, &*attribute.name))
}
_ => false,
}
}

fn is_attribute_value_unordered_set(&self, element: &Element, attribute_name: &JsWord) -> bool {
fn is_attribute_value_unordered_set(&self, element: &Element, attribute: &Attribute) -> bool {
if matches!(
*attribute_name,
attribute.name,
js_word!("class")
| js_word!("part")
| js_word!("itemprop")
Expand All @@ -507,17 +513,17 @@ impl Minifier<'_> {

match element.namespace {
Namespace::HTML => match element.tag_name {
js_word!("link") if *attribute_name == js_word!("blocking") => true,
js_word!("script") if *attribute_name == js_word!("blocking") => true,
js_word!("style") if *attribute_name == js_word!("blocking") => true,
js_word!("output") if *attribute_name == js_word!("for") => true,
js_word!("td") if *attribute_name == js_word!("headers") => true,
js_word!("th") if *attribute_name == js_word!("headers") => true,
js_word!("form") if *attribute_name == js_word!("rel") => true,
js_word!("a") if *attribute_name == js_word!("rel") => true,
js_word!("area") if *attribute_name == js_word!("rel") => true,
js_word!("link") if *attribute_name == js_word!("rel") => true,
js_word!("iframe") if *attribute_name == js_word!("sandbox") => true,
js_word!("link") if attribute.name == js_word!("blocking") => true,
js_word!("script") if attribute.name == js_word!("blocking") => true,
js_word!("style") if attribute.name == js_word!("blocking") => true,
js_word!("output") if attribute.name == js_word!("for") => true,
js_word!("td") if attribute.name == js_word!("headers") => true,
js_word!("th") if attribute.name == js_word!("headers") => true,
js_word!("form") if attribute.name == js_word!("rel") => true,
js_word!("a") if attribute.name == js_word!("rel") => true,
js_word!("area") if attribute.name == js_word!("rel") => true,
js_word!("link") if attribute.name == js_word!("rel") => true,
js_word!("iframe") if attribute.name == js_word!("sandbox") => true,
js_word!("link")
if self.element_has_attribute_with_value(
element,
Expand All @@ -527,19 +533,38 @@ impl Minifier<'_> {
js_word!("apple-touch-icon"),
js_word!("apple-touch-icon-precomposed"),
],
) && *attribute_name == js_word!("sizes") =>
) && attribute.name == js_word!("sizes") =>
{
true
}
_ => false,
},
Namespace::SVG => {
matches!(element.tag_name, js_word!("a") if *attribute_name == js_word!("rel"))
matches!(element.tag_name, js_word!("a") if attribute.name == js_word!("rel"))
}
_ => false,
}
}

fn is_crossorigin_attribute(&self, current_element: &Element, attribute: &Attribute) -> bool {
matches!(
(
current_element.namespace,
&current_element.tag_name,
&attribute.name,
),
(
Namespace::HTML,
&js_word!("img")
| &js_word!("audio")
| &js_word!("video")
| &js_word!("script")
| &js_word!("link"),
&js_word!("crossorigin"),
) | (Namespace::SVG, &js_word!("image"), &js_word!("crossorigin"))
)
}

fn element_has_attribute_with_value(
&self,
element: &Element,
Expand Down Expand Up @@ -2330,7 +2355,7 @@ impl VisitMut for Minifier<'_> {
if (matches!(i1.name, js_word!("id")) && value.is_empty())
|| (matches!(i1.name, js_word!("class") | js_word!("style"))
&& value.is_empty())
|| self.is_event_handler_attribute(&i1.name) && value.is_empty()
|| self.is_event_handler_attribute(i1) && value.is_empty()
{
remove_list.push(i);

Expand Down Expand Up @@ -2381,12 +2406,24 @@ impl VisitMut for Minifier<'_> {
n.visit_mut_children_with(self);

if let Some(value) = &n.value {
let current_element = match &self.current_element {
Some(current_element) => current_element,
_ => return,
};

if value.is_empty() {
if (self.options.collapse_boolean_attributes
&& self.is_boolean_attribute(current_element, n))
|| (self.options.normalize_attributes
&& self.is_crossorigin_attribute(current_element, n)
&& value.is_empty())
{
n.value = None;
}

return;
}

let current_element = self.current_element.as_ref().unwrap();

match (
current_element.namespace,
&current_element.tag_name,
Expand All @@ -2410,13 +2447,18 @@ impl VisitMut for Minifier<'_> {
) if self.options.normalize_attributes => {
n.value = Some(value.trim().to_ascii_lowercase().into());
}
_ if self.options.normalize_attributes
&& self.is_crossorigin_attribute(current_element, n)
&& value.to_ascii_lowercase() == js_word!("anonymous") =>
{
n.value = None;
}
_ if self.options.collapse_boolean_attributes
&& current_element.namespace == Namespace::HTML
&& self.is_boolean_attribute(current_element, &n.name) =>
&& self.is_boolean_attribute(current_element, n) =>
{
n.value = None;
}
_ if self.is_event_handler_attribute(&n.name) => {
_ if self.is_event_handler_attribute(n) => {
let mut value = value.to_string();

if self.options.normalize_attributes {
Expand All @@ -2443,7 +2485,7 @@ impl VisitMut for Minifier<'_> {
n.value = Some(js_word!(""));
}
_ if self.options.normalize_attributes
&& self.is_semicolon_separated_attribute(current_element, &n.name) =>
&& self.is_semicolon_separated_attribute(current_element, n) =>
{
n.value = Some(
value
Expand Down Expand Up @@ -2484,7 +2526,7 @@ impl VisitMut for Minifier<'_> {
n.value = Some(value.into());
}
_ if self.options.sort_space_separated_attribute_values
&& self.is_attribute_value_unordered_set(current_element, &n.name) =>
&& self.is_attribute_value_unordered_set(current_element, n) =>
{
let mut values = value.split_whitespace().collect::<Vec<_>>();

Expand All @@ -2493,7 +2535,7 @@ impl VisitMut for Minifier<'_> {
n.value = Some(values.join(" ").into());
}
_ if self.options.normalize_attributes
&& self.is_space_separated_attribute(current_element, &n.name) =>
&& self.is_space_separated_attribute(current_element, n) =>
{
n.value = Some(
value
Expand All @@ -2503,7 +2545,7 @@ impl VisitMut for Minifier<'_> {
.into(),
);
}
_ if self.is_comma_separated_attribute(current_element, &n.name) => {
_ if self.is_comma_separated_attribute(current_element, n) => {
let mut value = value.to_string();

if self.options.normalize_attributes {
Expand Down Expand Up @@ -2539,7 +2581,7 @@ impl VisitMut for Minifier<'_> {
n.value = Some(value.into());
}
}
_ if self.is_trimable_separated_attribute(current_element, &n.name) => {
_ if self.is_trimable_separated_attribute(current_element, n) => {
let mut value = value.to_string();

let fallback = |n: &mut Attribute| {
Expand Down
Expand Up @@ -42,5 +42,9 @@
<div Draggable></div>
<div draggable="Auto"></div>

<img src="test.png" alt="..." ismap>
<img src="test.png" alt="..." ismap="">
<img src="test.png" alt="..." ismap="ismap">

</body>
</html>
Expand Up @@ -33,4 +33,8 @@
<div draggable=false></div>
<div draggable=foo></div>
<div draggable></div>
<div draggable=Auto></div>
<div draggable=Auto></div>

<img src=test.png alt=... ismap>
<img src=test.png alt=... ismap>
<img src=test.png alt=... ismap>
@@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="test.css" crossorigin="anonymous">
</head>
<body>
<img src="test.png" alt="test" crossorigin="">
<img src="test.png" alt="test" crossorigin="unknown">
<img src="test.png" alt="test" crossorigin="anonymous">
<img src="test.png" alt="test" crossorigin="use-credentials">
<audio src="test.mp3" crossorigin="anonymous"></audio>
<video src="test.mp4" crossorigin="anonymous"></video>
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<image href="mdn_logo_only_color.png" height="200" width="200" crossorigin="anonymous" />
</svg>
<script
src="https://example.com/example-framework.js"
crossorigin="anonymous"></script>
</body>
</html>
@@ -0,0 +1,10 @@
<!doctype html><html lang=en><meta charset=UTF-8><title>Document</title><link rel=stylesheet href=test.css crossorigin><img src=test.png alt=test crossorigin>
<img src=test.png alt=test crossorigin=unknown>
<img src=test.png alt=test crossorigin>
<img src=test.png alt=test crossorigin=use-credentials>
<audio src=test.mp3 crossorigin></audio>
<video src=test.mp4 crossorigin></video>
<svg width=200 height=200>
<image href=mdn_logo_only_color.png height=200 width=200 crossorigin/>
</svg>
<script src=https://example.com/example-framework.js crossorigin></script>
@@ -1,4 +1,4 @@
<!doctype html><title>Party Coffee Cake</title><!--[if lte IE 8]><script>alert("ie8!test")</script><![endif]--><script type=application/ld+json>{"@context":"https://schema.org/","@type":"Recipe","author":{"@type":"Person","name":"Mary Stone"},"datePublished":"2018-03-10","description":"This coffee cake is awesome and perfect for parties.","name":"Party Coffee Cake","prepTime":"PT20M"}</script><script type=application/ld+json crossorigin=anonymous>{"@context":"https://schema.org/","@type":"Recipe","author":{"@type":"Person","name":"Mary Stone"},"datePublished":"2018-03-10","description":"This coffee cake is awesome and perfect for parties.","name":"Party Coffee Cake","prepTime":"PT20M"}</script><script type=application/ld+json crossorigin=anonymous>
<!doctype html><title>Party Coffee Cake</title><!--[if lte IE 8]><script>alert("ie8!test")</script><![endif]--><script type=application/ld+json>{"@context":"https://schema.org/","@type":"Recipe","author":{"@type":"Person","name":"Mary Stone"},"datePublished":"2018-03-10","description":"This coffee cake is awesome and perfect for parties.","name":"Party Coffee Cake","prepTime":"PT20M"}</script><script type=application/ld+json crossorigin>{"@context":"https://schema.org/","@type":"Recipe","author":{"@type":"Person","name":"Mary Stone"},"datePublished":"2018-03-10","description":"This coffee cake is awesome and perfect for parties.","name":"Party Coffee Cake","prepTime":"PT20M"}</script><script type=application/ld+json crossorigin>
{
broken
</script><script type=unknown>
Expand Down

1 comment on commit 782da5c

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: 782da5c Previous: 4c293f9 Ratio
es/full/bugs-1 372969 ns/iter (± 197640) 366183 ns/iter (± 36637) 1.02
es/full/minify/libraries/antd 1906719838 ns/iter (± 60376461) 1948103139 ns/iter (± 63621037) 0.98
es/full/minify/libraries/d3 432136029 ns/iter (± 25410991) 438779909 ns/iter (± 34882646) 0.98
es/full/minify/libraries/echarts 1617301679 ns/iter (± 41457861) 1623728382 ns/iter (± 72599388) 1.00
es/full/minify/libraries/jquery 115021948 ns/iter (± 4587068) 107646934 ns/iter (± 3959842) 1.07
es/full/minify/libraries/lodash 132318824 ns/iter (± 6858028) 135957770 ns/iter (± 14182099) 0.97
es/full/minify/libraries/moment 69577365 ns/iter (± 6560088) 67129457 ns/iter (± 9895927) 1.04
es/full/minify/libraries/react 22625404 ns/iter (± 917119) 21512068 ns/iter (± 2240942) 1.05
es/full/minify/libraries/terser 355190095 ns/iter (± 21482023) 358762233 ns/iter (± 35793756) 0.99
es/full/minify/libraries/three 582006848 ns/iter (± 62832958) 594088565 ns/iter (± 16770006) 0.98
es/full/minify/libraries/typescript 3613809306 ns/iter (± 66494279) 3649113147 ns/iter (± 98081415) 0.99
es/full/minify/libraries/victory 862079462 ns/iter (± 42409133) 853269804 ns/iter (± 10586100) 1.01
es/full/minify/libraries/vue 172266290 ns/iter (± 23209983) 171201767 ns/iter (± 8209519) 1.01
es/full/codegen/es3 32783 ns/iter (± 422) 33366 ns/iter (± 1018) 0.98
es/full/codegen/es5 32785 ns/iter (± 835) 33439 ns/iter (± 933) 0.98
es/full/codegen/es2015 32730 ns/iter (± 945) 33253 ns/iter (± 2287) 0.98
es/full/codegen/es2016 32702 ns/iter (± 1034) 33446 ns/iter (± 342) 0.98
es/full/codegen/es2017 32733 ns/iter (± 637) 33322 ns/iter (± 286) 0.98
es/full/codegen/es2018 32625 ns/iter (± 623) 33394 ns/iter (± 423) 0.98
es/full/codegen/es2019 32814 ns/iter (± 603) 33462 ns/iter (± 735) 0.98
es/full/codegen/es2020 32676 ns/iter (± 750) 33489 ns/iter (± 1343) 0.98
es/full/all/es3 212479827 ns/iter (± 11982743) 211984460 ns/iter (± 18117518) 1.00
es/full/all/es5 202692639 ns/iter (± 13238644) 199474596 ns/iter (± 19521856) 1.02
es/full/all/es2015 160011448 ns/iter (± 11551966) 155228762 ns/iter (± 6761905) 1.03
es/full/all/es2016 159959199 ns/iter (± 9424651) 155341844 ns/iter (± 8340737) 1.03
es/full/all/es2017 157933475 ns/iter (± 9065317) 154040122 ns/iter (± 23910690) 1.03
es/full/all/es2018 158479888 ns/iter (± 10422918) 152436554 ns/iter (± 13654846) 1.04
es/full/all/es2019 148375646 ns/iter (± 8929590) 151156373 ns/iter (± 9532660) 0.98
es/full/all/es2020 150443719 ns/iter (± 9757128) 144890990 ns/iter (± 9623244) 1.04
es/full/parser 734967 ns/iter (± 22701) 744755 ns/iter (± 41547) 0.99
es/full/base/fixer 26141 ns/iter (± 1232) 26024 ns/iter (± 4982) 1.00
es/full/base/resolver_and_hygiene 92924 ns/iter (± 3599) 92872 ns/iter (± 8751) 1.00
serialization of ast node 214 ns/iter (± 8) 215 ns/iter (± 6) 1.00
serialization of serde 220 ns/iter (± 8) 219 ns/iter (± 23) 1.00

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.