diff --git a/crates/swc_html_minifier/src/lib.rs b/crates/swc_html_minifier/src/lib.rs index 563aad1aa49..f7e2b969756 100644 --- a/crates/swc_html_minifier/src/lib.rs +++ b/crates/swc_html_minifier/src/lib.rs @@ -173,6 +173,8 @@ static ALLOW_TO_TRIM_HTML_ATTRIBUTES: &[(&str, &str)] = &[ ("object", "usemap"), ]; +static ALLOW_TO_TRIM_SVG_ATTRIBUTES: &[(&str, &str)] = &[("a", "href")]; + static COMMA_SEPARATED_HTML_ATTRIBUTES: &[(&str, &str)] = &[ ("img", "srcset"), ("source", "srcset"), @@ -398,6 +400,9 @@ impl Minifier<'_> { Namespace::HTML => { ALLOW_TO_TRIM_HTML_ATTRIBUTES.contains(&(&element.tag_name, attribute_name)) } + Namespace::SVG => { + ALLOW_TO_TRIM_SVG_ATTRIBUTES.contains(&(&element.tag_name, attribute_name)) + } _ => false, } } @@ -658,6 +663,16 @@ impl Minifier<'_> { } } + fn is_javascript_url_element(&self, element: &Element) -> bool { + match (element.namespace, &element.tag_name) { + (Namespace::HTML | Namespace::SVG, &js_word!("a")) => return true, + (Namespace::HTML, &js_word!("iframe")) => return true, + _ => {} + } + + false + } + fn is_preserved_comment(&self, data: &str) -> bool { if let Some(preserve_comments) = &self.options.preserve_comments { return preserve_comments.iter().any(|regex| regex.is_match(data)); @@ -2419,18 +2434,42 @@ impl VisitMut for Minifier<'_> { _ if self.is_trimable_separated_attribute(current_element, &n.name) => { let mut value = value.to_string(); - if self.options.normalize_attributes { - value = value.trim().to_string(); - } + let fallback = |n: &mut Attribute| { + if self.options.normalize_attributes { + n.value = Some(value.trim().into()); + } + }; if self.need_minify_css() && n.name == js_word!("style") && !value.is_empty() { - if let Some(minified) = - self.minify_css(value, CssMinificationMode::ListOfDeclarations) + let value = value.trim(); + + if let Some(minified) = self + .minify_css(value.to_string(), CssMinificationMode::ListOfDeclarations) { n.value = Some(minified.into()); + } else { + fallback(n); + } + } else if self.need_minify_js() + && self.is_javascript_url_element(current_element) + { + if value.trim().to_lowercase().starts_with("javascript:") { + value = value.trim().chars().skip(11).collect(); + + if let Some(minified) = self.minify_js(value, false, true) { + let mut with_javascript = + String::with_capacity(11 + minified.len()); + + with_javascript.push_str("javascript:"); + with_javascript.push_str(&minified); + + n.value = Some(with_javascript.into()); + } + } else { + fallback(n); } } else { - n.value = Some(value.into()); + fallback(n); } } _ if self.options.minify_additional_attributes.is_some() => { diff --git a/crates/swc_html_minifier/tests/fixture/attribute/javascript-urls-1/config.json b/crates/swc_html_minifier/tests/fixture/attribute/javascript-urls-1/config.json new file mode 100644 index 00000000000..9642d5c6c8a --- /dev/null +++ b/crates/swc_html_minifier/tests/fixture/attribute/javascript-urls-1/config.json @@ -0,0 +1,3 @@ +{ + "normalizeAttributes": false +} \ No newline at end of file diff --git a/crates/swc_html_minifier/tests/fixture/attribute/javascript-urls-1/input.html b/crates/swc_html_minifier/tests/fixture/attribute/javascript-urls-1/input.html new file mode 100644 index 00000000000..aa64850e012 --- /dev/null +++ b/crates/swc_html_minifier/tests/fixture/attribute/javascript-urls-1/input.html @@ -0,0 +1,21 @@ + + +
+