fix(html/minifier): Fix bugs of the smart mode (#5093)

This commit is contained in:
Alexander Akait 2022-07-05 09:04:12 +03:00 committed by GitHub
parent 09d7f7aa27
commit 5932a0a2ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 616 additions and 211 deletions

View File

@ -1,7 +1,5 @@
#![deny(clippy::all)]
use std::mem::take;
use once_cell::sync::Lazy;
use serde_json::Value;
use swc_atoms::{js_word, JsWord};
@ -656,6 +654,15 @@ impl Minifier {
!matches!(self.collapse_whitespaces, CollapseWhitespaces::None)
}
fn is_custom_element(&self, tag_name: &str) -> bool {
// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
match tag_name {
"annotation-xml" | "color-profile" | "font-face" | "font-face-src"
| "font-face-uri" | "font-face-format" | "font-face-name" | "missing-glyph" => false,
_ => matches!(tag_name.chars().next(), Some('a'..='z')) && tag_name.contains('-'),
}
}
fn get_display(&self, namespace: Namespace, tag_name: &str) -> Display {
match namespace {
Namespace::HTML => {
@ -715,27 +722,40 @@ impl Minifier {
_ => Display::Inline,
}
}
Namespace::SVG => match tag_name {
"text" | "foreignObject" => Display::Block,
_ => Display::Inline,
},
_ => Display::Inline,
}
}
fn is_element_displayed(&self, namespace: Namespace, tag_name: &str) -> bool {
match namespace {
Namespace::HTML => {
// https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#metadata_content
//
// Excluded:
// `noscript` - can be displayed if JavaScript disabled
// `script` - can insert markup using `document.write`
Namespace::HTML => !matches!(
!matches!(
tag_name,
"base" | "command" | "link" | "meta" | "style" | "title"
),
"base" | "command" | "link" | "meta" | "style" | "title" | "template"
)
}
Namespace::SVG => !matches!(tag_name, "style"),
_ => true,
}
}
fn remove_leading_and_trailing_whitespaces(&self, children: &mut Vec<Child>) {
fn remove_leading_and_trailing_whitespaces(
&self,
children: &mut Vec<Child>,
only_first: bool,
only_last: bool,
) {
if only_first {
if let Some(last) = children.first_mut() {
match last {
Child::Text(text) => {
@ -751,12 +771,14 @@ impl Minifier {
children,
..
}) if get_white_space(*namespace, tag_name) == WhiteSpace::Normal => {
self.remove_leading_and_trailing_whitespaces(children);
self.remove_leading_and_trailing_whitespaces(children, true, false);
}
_ => {}
}
}
}
if only_last {
if let Some(last) = children.last_mut() {
match last {
Child::Text(text) => {
@ -772,33 +794,15 @@ impl Minifier {
children,
..
}) if get_white_space(*namespace, tag_name) == WhiteSpace::Normal => {
self.remove_leading_and_trailing_whitespaces(children);
self.remove_leading_and_trailing_whitespaces(children, false, true);
}
_ => {}
}
}
}
fn get_deep_last_text_element<'a>(&self, node: &'a Child) -> Option<&'a Text> {
match node {
Child::Text(text) => Some(text),
Child::Element(Element {
namespace,
tag_name,
children,
..
}) if get_white_space(*namespace, tag_name) == WhiteSpace::Normal => {
if let Some(last) = children.last() {
self.get_deep_last_text_element(last)
} else {
None
}
}
_ => None,
}
}
fn get_prev_non_comment_node<'a>(
fn get_prev_displayed_node<'a>(
&self,
children: &'a Vec<Child>,
index: usize,
@ -806,15 +810,96 @@ impl Minifier {
let prev = children.get(index);
match prev {
Some(Child::Comment(_)) if index >= 1 => {
self.get_prev_non_comment_node(children, index - 1)
Some(Child::Comment(_)) => {
if index >= 1 {
self.get_prev_displayed_node(children, index - 1)
} else {
None
}
}
Some(Child::Element(element)) => {
if !self.is_element_displayed(element.namespace, &element.tag_name) && index >= 1 {
self.get_prev_displayed_node(children, index - 1)
} else if !element.children.is_empty() {
self.get_prev_displayed_node(&element.children, element.children.len() - 1)
} else {
prev
}
}
Some(_) => prev,
_ => None,
}
}
fn get_next_non_comment_node<'a>(
fn get_last_displayed_text_node<'a>(
&self,
children: &'a Vec<Child>,
index: usize,
) -> Option<&'a Text> {
let prev = children.get(index);
match prev {
Some(Child::Comment(_)) => {
if index >= 1 {
self.get_last_displayed_text_node(children, index - 1)
} else {
None
}
}
Some(Child::Element(element)) => {
if !self.is_element_displayed(element.namespace, &element.tag_name) && index >= 1 {
self.get_last_displayed_text_node(children, index - 1)
} else if !element.children.is_empty() {
for index in (0..=element.children.len() - 1).rev() {
if let Some(text) =
self.get_last_displayed_text_node(&element.children, index)
{
return Some(text);
}
}
None
} else {
None
}
}
Some(Child::Text(text)) => Some(text),
_ => None,
}
}
fn get_first_displayed_text_node<'a>(
&self,
children: &'a Vec<Child>,
index: usize,
) -> Option<&'a Text> {
let next = children.get(index);
match next {
Some(Child::Comment(_)) => self.get_first_displayed_text_node(children, index + 1),
Some(Child::Element(element)) => {
if !self.is_element_displayed(element.namespace, &element.tag_name) && index >= 1 {
self.get_first_displayed_text_node(children, index - 1)
} else if !element.children.is_empty() {
for index in 0..=element.children.len() - 1 {
if let Some(text) =
self.get_first_displayed_text_node(&element.children, index)
{
return Some(text);
}
}
None
} else {
None
}
}
Some(Child::Text(text)) => Some(text),
_ => None,
}
}
fn get_next_displayed_node<'a>(
&self,
children: &'a Vec<Child>,
index: usize,
@ -822,23 +907,17 @@ impl Minifier {
let next = children.get(index);
match next {
Some(Child::Comment(_)) => self.get_next_non_comment_node(children, index + 1),
Some(Child::Comment(_)) => self.get_next_displayed_node(children, index + 1),
Some(Child::Element(element))
if !self.is_element_displayed(element.namespace, &element.tag_name) =>
{
self.get_next_displayed_node(children, index + 1)
}
Some(_) => next,
_ => None,
}
}
fn get_next_text_node<'a>(&self, children: &'a Vec<Child>, index: usize) -> Option<&'a Child> {
let next = children.get(index);
match next {
Some(Child::Text(_)) => next,
Some(Child::Element(_)) => None,
Some(_) => self.get_next_text_node(children, index + 1),
_ => None,
}
}
fn get_whitespace_minification_for_tag(
&self,
namespace: Namespace,
@ -966,17 +1045,17 @@ impl Minifier {
true
}
fn minify_children(&mut self, children: &mut Vec<Child>) {
fn minify_children(&mut self, children: &Vec<Child>) -> Vec<Child> {
let (namespace, tag_name) = match &self.current_element {
Some(element) => (element.namespace, &element.tag_name),
_ => return,
_ => {
unreachable!();
}
};
let mode = self.get_whitespace_minification_for_tag(namespace, tag_name);
let child_will_be_retained = |child: &mut Child,
prev: Option<&Child>,
next: Option<&Child>| {
let child_will_be_retained = |child: &mut Child, children: &Vec<Child>, index: usize| {
match child {
Child::Comment(comment) if self.remove_comments => {
self.is_preserved_comment(&comment.data)
@ -989,7 +1068,8 @@ impl Minifier {
|| (element.namespace == Namespace::HTML
&& &*element.tag_name == "noscript"))
&& element.attributes.is_empty()
&& self.empty_children(&element.children) =>
&& self.empty_children(&element.children)
&& element.content.is_none() =>
{
false
}
@ -1016,15 +1096,19 @@ impl Minifier {
let mut is_smart_right_trim = false;
if self.collapse_whitespaces == CollapseWhitespaces::Smart {
let prev_display = if let Some(Child::Element(Element {
let prev = if index >= 1 {
children.get(index - 1)
} else {
None
};
let prev_display = match prev {
Some(Child::Element(Element {
namespace,
tag_name,
..
})) = &prev
{
Some(self.get_display(*namespace, tag_name))
} else {
None
})) => Some(self.get_display(*namespace, tag_name)),
Some(Child::Comment(_)) => Some(Display::None),
_ => None,
};
is_smart_left_trim = match prev_display {
@ -1033,8 +1117,8 @@ impl Minifier {
// `Display::Block` - `display: block flow`
// `Display::ListItem` - `display: block flow list-item`
// `Display::Table` - `display: block table`
// + internal table display (only whitespace characters allowed
// there)
//
// + internal table display (only whitespace characters allowed there)
Some(
Display::Block
| Display::ListItem
@ -1048,36 +1132,79 @@ impl Minifier {
| Display::TableRowGroup
| Display::TableFooterGroup,
) => true,
// These elements are not displayed
Some(Display::None) if prev.is_some() => {
if let Some(Child::Element(Element {
namespace,
tag_name,
..
})) = &prev
{
!self.is_element_displayed(*namespace, tag_name)
} else {
true
}
}
// Elements are not displayed
// And
// Inline box
Some(Display::Inline) => {
if let Some(prev) = &prev {
let deep = self.get_deep_last_text_element(prev);
Some(Display::None) | Some(Display::Inline) => {
// A custom element can contain any elements, we cannot predict the
// behavior of spaces
let is_custom_element = if let Some(Child::Element(element)) = &prev
{
self.is_custom_element(&*element.tag_name)
} else {
false
};
if is_custom_element {
false
} else {
match &self.get_prev_displayed_node(children, index - 1) {
Some(Child::Text(text)) => {
text.data.ends_with(is_whitespace)
}
Some(Child::Element(element)) => {
let deep = if !element.children.is_empty() {
self.get_last_displayed_text_node(
&element.children,
element.children.len() - 1,
)
} else {
None
};
if let Some(deep) = deep {
deep.data.ends_with(is_whitespace)
} else {
false
}
}
_ => {
let parent_display =
self.get_display(namespace, tag_name);
match parent_display {
Display::Inline => {
if let Some(Child::Text(Text {
data, ..
})) = &self.latest_element
{
data.ends_with(is_whitespace)
} else {
false
}
}
_ => true,
}
}
}
}
}
// Inline level containers and etc
Some(_) => false,
None => {
// Template can be used in any place, so let's keep whitespaces
//
// For custom elements - an unnamed `<slot>` will be filled with all
// of the custom element's top-level
// child nodes that do not have the slot
// attribute. This includes text nodes.
// Also they can be used for custom logic
if (namespace == Namespace::HTML && tag_name == "template")
|| self.is_custom_element(tag_name)
{
false
} else {
let parent_display = self.get_display(namespace, tag_name);
match parent_display {
@ -1093,17 +1220,18 @@ impl Minifier {
_ => true,
}
}
}
};
let next_display = if let Some(Child::Element(Element {
let next = children.get(index + 1);
let next_display = match next {
Some(Child::Element(Element {
namespace,
tag_name,
..
})) = &next
{
Some(self.get_display(*namespace, tag_name))
} else {
None
})) => Some(self.get_display(*namespace, tag_name)),
Some(Child::Comment(_)) => Some(Display::None),
_ => None,
};
is_smart_right_trim = match next_display {
@ -1112,8 +1240,8 @@ impl Minifier {
// `Display::Block` - `display: block flow`
// `Display::ListItem` - `display: block flow list-item`
// `Display::Table` - `display: block table`
// + internal table display (only whitespace characters allowed
// there)
//
// + internal table display (only whitespace characters allowed there)
Some(
Display::Block
| Display::ListItem
@ -1128,24 +1256,40 @@ impl Minifier {
| Display::TableFooterGroup,
) => true,
// These elements are not displayed
Some(Display::None) if prev.is_some() => {
if let Some(Child::Element(Element {
namespace,
tag_name,
..
})) = &next
{
!self.is_element_displayed(*namespace, tag_name)
Some(Display::None) => {
match &self.get_next_displayed_node(children, index + 1) {
Some(Child::Text(text)) => text.data.starts_with(is_whitespace),
Some(Child::Element(element)) => {
let deep = self
.get_first_displayed_text_node(&element.children, 0);
if let Some(deep) = deep {
!deep.data.starts_with(is_whitespace)
} else {
true
false
}
}
_ => {
let parent_display = self.get_display(namespace, tag_name);
!matches!(parent_display, Display::Inline)
}
}
}
Some(_) => false,
None => {
// Template can be used in any place, so let's keep whitespaces
let is_template =
namespace == Namespace::HTML && tag_name == "template";
if is_template {
false
} else {
let parent_display = self.get_display(namespace, tag_name);
!matches!(parent_display, Display::Inline)
}
}
};
}
@ -1177,56 +1321,59 @@ impl Minifier {
}
};
let cloned_children = children.clone();
let mut new_children = Vec::with_capacity(children.len());
let mut index = 0;
let mut pending_text = vec![];
for (index, child) in children.iter().enumerate() {
let mut child = child.clone();
children.retain_mut(|child| {
match child {
Child::Text(text)
if self
.get_next_text_node(&cloned_children, index + 1)
.is_some()
&& !child_will_be_retained(
&mut cloned_children.get(index + 1).cloned().unwrap(),
self.get_prev_non_comment_node(&cloned_children, index),
self.get_next_non_comment_node(&cloned_children, index + 2),
) =>
{
pending_text.push(text.data.clone());
// Merge adjacent text nodes
let merged = match &mut child {
Child::Text(text) => {
if let Some(Child::Text(prev_text)) = new_children.last_mut() {
let mut new_data =
String::with_capacity(prev_text.data.len() + text.data.len());
index += 1;
new_data.push_str(&prev_text.data);
new_data.push_str(&text.data);
return false;
}
Child::Text(text) if !pending_text.is_empty() => {
let mut new_value = String::new();
text.data = new_data.into();
for text in take(&mut pending_text) {
new_value.push_str(&text);
}
new_children.pop();
new_value.push_str(&text.data);
text.data = new_value.into();
}
_ => {}
}
let prev = if index >= 1 {
self.get_prev_non_comment_node(&cloned_children, index - 1)
true
} else {
None
false
}
}
_ => false,
};
let next = self.get_next_non_comment_node(&cloned_children, index + 1);
let result = child_will_be_retained(child, prev, next);
let mut merged_children = new_children.clone();
index += 1;
let (merged_children, offset) = if merged {
merged_children.push(child.clone());
result
});
let offset = merged_children.len() - 1;
merged_children.extend_from_slice(&children[index + 1..]);
(merged_children, offset)
} else {
let offset = merged_children.len();
merged_children.extend_from_slice(&children[index..]);
(merged_children, offset)
};
let result = child_will_be_retained(&mut child, &merged_children, offset);
if result {
new_children.push(child);
}
}
new_children
}
fn get_attribute_value(&self, attributes: &Vec<Attribute>, name: &str) -> Option<JsWord> {
@ -1548,7 +1695,7 @@ impl Minifier {
let mut document_or_document_fragment = match mode {
HtmlMinificationMode::ConditionalComments => {
// Emulate content inside conditional comments like content inside the
// `template` element
// `template` element, because it can be used in any place in source code
context_element = Some(Element {
span: Default::default(),
tag_name: "template".into(),
@ -1660,7 +1807,7 @@ impl VisitMut for Minifier {
}
fn visit_mut_document_fragment(&mut self, n: &mut DocumentFragment) {
self.minify_children(&mut n.children);
n.children = self.minify_children(&n.children);
n.visit_mut_children_with(self);
}
@ -1682,9 +1829,14 @@ impl VisitMut for Minifier {
self.current_element = None;
if self.need_collapse_whitespace() {
if self.collapse_whitespaces == CollapseWhitespaces::Smart {
match n {
Child::Text(_) | Child::Element(_) => {
self.latest_element = Some(n.clone());
}
_ => {}
}
}
}
fn visit_mut_element(&mut self, n: &mut Element) {
@ -1705,7 +1857,7 @@ impl VisitMut for Minifier {
self.descendant_of_pre = get_white_space(n.namespace, &n.tag_name) == WhiteSpace::Pre;
}
self.minify_children(&mut n.children);
n.children = self.minify_children(&n.children);
n.visit_mut_children_with(self);
@ -1714,7 +1866,7 @@ impl VisitMut for Minifier {
&& &*n.tag_name == "body"
&& self.need_collapse_whitespace()
{
self.remove_leading_and_trailing_whitespaces(&mut n.children);
self.remove_leading_and_trailing_whitespaces(&mut n.children, true, true);
}
if self.need_collapse_whitespace() {

View File

@ -1,9 +1,4 @@
<!doctype html><html lang=en><body>
<svg>
<!doctype html><html lang=en><svg>
</svg>

View File

@ -432,5 +432,14 @@
</style>
</svg>
<div>
<span>test</span> a <!-- test -->b <span>test</span>
<span>test</span> a<!-- test --> b <span>test</span>
<span>test</span> a <!-- test --> b <span>test</span>
</div>
<div><foo-bar> <span>test</span> </foo-bar> <foo-bar> <span>test</span> </foo-bar></div>
<div><svg> <linearGradient id=gradient /> </svg> <span>a</span></div>
</body>
</html>

View File

@ -29,4 +29,4 @@
foo
baz
</pre><div>a <input> c</div><div>Empty</div><!--[if lte IE 6]> <span>A</span> <span title=" sigificant whitespace ">blah blah</span><![endif]--><div><a href=#> <span><b>foo </b><i> bar </i></span></a></div><div>a b</div><div>a b c d</div><div>text</div><span> text </span><span> text </span><div><span>test</span><span>test</span></div><div><span>test</span> <command>test</command> <span>test</span></div><div><span>test</span><link rel=stylesheet href=""><span>test</span></div><div><span>test</span><meta name=content><span>test</span></div><div><span>test</span> <script>console.log("test")</script> <span>test</span></div><div><span>test</span><style>a{color:red}</style><span>test</span></div><div><span>test</span><title>test</title><span>test</span></div><div><meta name=test><meta name=test></div><div><link rel=stylesheet href=""><link rel=stylesheet href=""></div><div><script>console.log("test")</script> <script>console.log("test")</script></div><div><script>console.log("test")</script> <span>test</span> <script>console.log("test")</script></div><div><style>a{color:red}</style><style>a{color:red}</style></div><div><script>console.log("test")</script><style>a{color:red}</style></div><div><span itemscope><meta itemprop=name content="The Castle">test</span> <span>test</span></div><div><meta name=test></div><div><style>a{color:red}</style></div><div><meta name=test><div>test</div><meta name=test></div><div><meta name=test><span>test</span><meta name=test></div><svg> <title>test</title> <metadata>test</metadata> <desc>test</desc> </svg><svg> <a>test</a> <a>test</a> </svg><svg> <text x=20 y=35><tspan font-weight=bold fill=red>This is bold and red</tspan> <tspan font-weight=bold fill=red>This is bold and red</tspan> </text></svg><svg> <tspan>test</tspan> <foreignObject>test</foreignObject> </svg><svg> <text x=20 y=35><tspan font-weight=bold fill=red>This is bold and red</tspan> <tspan font-weight=bold fill=red>This is bold and red</tspan> </text></svg><svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice" style=width:100%;height:100%;position:absolute;top:0;left:0;z-index:-1> <linearGradient id=gradient><stop class=begin offset=0% /><stop class=end offset=100% /></linearGradient><rect x=0 y=0 width=100 height=100 style=fill:url(#gradient) /> <circle cx=50 cy=50 r=30 style=fill:url(#gradient) /> </svg><svg> <script>console.log("test")</script></svg><svg><style>a{color:red}</style></svg>
</pre><div>a <input> c</div><div>Empty</div><!--[if lte IE 6]> <span>A</span> <span title=" sigificant whitespace ">blah blah</span> <![endif]--><div><a href=#> <span><b>foo </b><i> bar </i></span></a></div><div>a b</div><div>a b c d</div><div>text</div><span> text </span><span> text </span><div><span>test</span> <span>test</span></div><div><span>test</span> <command>test</command><span>test</span></div><div><span>test</span><link rel=stylesheet href=""> <span>test</span></div><div><span>test</span><meta name=content> <span>test</span></div><div><span>test</span><script>console.log("test")</script> <span>test</span></div><div><span>test</span><style>a{color:red}</style> <span>test</span></div><div><span>test</span><title>test</title> <span>test</span></div><div><meta name=test><meta name=test></div><div><link rel=stylesheet href=""><link rel=stylesheet href=""></div><div><script>console.log("test")</script><script>console.log("test")</script></div><div><script>console.log("test")</script> <span>test</span><script>console.log("test")</script></div><div><style>a{color:red}</style><style>a{color:red}</style></div><div><script>console.log("test")</script><style>a{color:red}</style></div><div><span itemscope><meta itemprop=name content="The Castle">test</span> <span>test</span></div><div><meta name=test></div><div><style>a{color:red}</style></div><div><meta name=test><div>test</div><meta name=test></div><div><meta name=test> <span>test</span><meta name=test></div><svg> <title>test</title> <metadata>test</metadata> <desc>test</desc> </svg><svg> <a>test</a> <a>test</a> </svg><svg><text x=20 y=35><tspan font-weight=bold fill=red>This is bold and red</tspan> <tspan font-weight=bold fill=red>This is bold and red</tspan></text></svg><svg> <tspan>test</tspan><foreignObject>test</foreignObject></svg><svg><text x=20 y=35><tspan font-weight=bold fill=red>This is bold and red</tspan> <tspan font-weight=bold fill=red>This is bold and red</tspan></text></svg><svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice" style=width:100%;height:100%;position:absolute;top:0;left:0;z-index:-1> <linearGradient id=gradient><stop class=begin offset=0% /><stop class=end offset=100% /></linearGradient><rect x=0 y=0 width=100 height=100 style=fill:url(#gradient) /> <circle cx=50 cy=50 r=30 style=fill:url(#gradient) /> </svg><svg> <script>console.log("test")</script></svg><svg> <style>a{color:red}</style></svg><div><span>test</span> a b <span>test</span> <span>test</span> a b <span>test</span> <span>test</span> a b <span>test</span></div><div><foo-bar> <span>test</span> </foo-bar> <foo-bar> <span>test</span> </foo-bar></div><div><svg> <linearGradient id=gradient /> </svg><span>a</span></div>

View File

@ -1,3 +1,6 @@
<!doctype html>
<html lang="en">
<span> <!-- test --> text <!-- test --></span> <span><!-- test --> text <!-- test --></span>
<span> <!-- test --> text <!-- test --> </span> <span><!-- test --> text <!-- test --></span>
<span> <!-- test --> text <!-- test --></span> <span> <!-- test --> text <!-- test --></span>
<span> <!-- test --> text <!-- test --> </span> <span> <!-- test --> text <!-- test --></span>

View File

@ -1 +1 @@
<!doctype html><html lang=en><span>text</span> <span>text</span>
<!doctype html><html lang=en><span>text </span><span> text </span><span> text </span><span> text </span><span> text </span><span> text </span><span> text </span><span> text</span>

View File

@ -0,0 +1,3 @@
{
"collapseWhitespaces": "smart"
}

View File

@ -0,0 +1,3 @@
<!doctype html>
<html lang="en">
<span> <span><!-- test --> text <!-- test --> </span></span> <span><span> <!-- test --> text <!-- test --></span> </span>

View File

@ -0,0 +1 @@
<!doctype html><html lang=en><span><span>text </span></span><span><span> text</span></span>

View File

@ -0,0 +1,3 @@
{
"collapseWhitespaces": "smart"
}

View File

@ -0,0 +1,3 @@
<!doctype html>
<html lang="en">
<span> <div><!-- test --> text <!-- test --> </div></span> <span><div> <!-- test --> text <!-- test --></div> </span>

View File

@ -0,0 +1 @@
<!doctype html><html lang=en><span><div>text</div></span><span><div>text</div></span>

View File

@ -0,0 +1,3 @@
{
"collapseWhitespaces": "smart"
}

View File

@ -0,0 +1,3 @@
<!doctype html>
<html lang="en">
<span> <span><!-- test --> text <!-- test --> </span><meta name="test"><!-- test --></span> <span><!-- test --><meta name="test"><span> <!-- test --> text <!-- test --></span> </span>

View File

@ -0,0 +1 @@
<!doctype html><html lang=en><span><span>text </span><meta name=test></span><span><meta name=test><span> text</span></span>

View File

@ -0,0 +1,3 @@
{
"collapseWhitespaces": "smart"
}

View File

@ -0,0 +1,3 @@
<!doctype html>
<html lang="en">
<foo-bar><span>test</span> a </foo-bar> b <foo-bar> c <span> test</span></foo-bar>

View File

@ -0,0 +1 @@
<!doctype html><html lang=en><foo-bar><span>test</span> a </foo-bar> b <foo-bar> c <span>test</span></foo-bar>

View File

@ -0,0 +1,3 @@
{
"collapseWhitespaces": "smart"
}

View File

@ -0,0 +1,3 @@
<!doctype html>
<html lang="en">
<foo-bar> <span>test</span> </foo-bar> <foo-bar> <span>test</span> </foo-bar>

View File

@ -0,0 +1 @@
<!doctype html><html lang=en><foo-bar><span>test</span> </foo-bar> <foo-bar> <span>test</span></foo-bar>

View File

@ -0,0 +1,3 @@
{
"collapseWhitespaces": "smart"
}

View File

@ -0,0 +1,18 @@
<!doctype html>
<div><span>test</span> <meta name="test"> <span>test</span></div>
<div><span>test</span> a <meta name="test"> <span>test</span></div>
<div><span>test</span> <meta name="test"> b <span>test</span></div>
<div><span>test</span> a <meta name="test"> b <span>test</span></div>
<div> <meta name="test"> <span>test</span> <meta name="test"> <span>test</span> <meta name="test"> </div>
<div> <span>test</span> <meta name="test"><meta name="test"><meta name="test"> <span>test</span> </div>
<div> <span>test</span> <meta name="test"> <meta name="test"> <meta name="test"> <span>test</span> </div>
<div><span>test</span> <template></template> <span>test</span></div>
<div><div>test</div> <meta name="test"> <div>test</div></div>
<div><img src="" alt=""> <meta name="test"> <img src="" alt=""></div>
<div><meta name="test"> <span>test</span> <meta name="test"></div>
<div><span>test</span> a<meta name="test"> b <span>test</span></div>
<div><span>test</span> a <meta name="test">b <span>test</span></div>
<div><span>test</span> a <meta name="test"> b <span>test</span></div>
<div><span>test</span> a<meta name="test">b <span>test</span></div>
<div> <meta name="test"> <span>test</span> <meta name="test"> <span>test</span> <meta name="test"> </div>
<div><span>test </span> a <meta name="test"> b <span>test</span></div>

View File

@ -0,0 +1 @@
<!doctype html><div><span>test</span><meta name=test> <span>test</span></div><div><span>test</span> a<meta name=test> <span>test</span></div><div><span>test</span><meta name=test> b <span>test</span></div><div><span>test</span> a<meta name=test> b <span>test</span></div><div><meta name=test> <span>test</span><meta name=test> <span>test</span><meta name=test></div><div><span>test</span><meta name=test><meta name=test><meta name=test> <span>test</span></div><div><span>test</span><meta name=test><meta name=test><meta name=test> <span>test</span></div><div><span>test</span><template></template> <span>test</span></div><div><div>test</div><meta name=test><div>test</div></div><div><img src="" alt=""><meta name=test> <img src="" alt=""></div><div><meta name=test> <span>test</span><meta name=test></div><div><span>test</span> a<meta name=test> b <span>test</span></div><div><span>test</span> a <meta name=test>b <span>test</span></div><div><span>test</span> a<meta name=test> b <span>test</span></div><div><span>test</span> a<meta name=test>b <span>test</span></div><div><meta name=test> <span>test</span><meta name=test> <span>test</span><meta name=test></div><div><span>test </span>a<meta name=test> b <span>test</span></div>

View File

@ -0,0 +1,3 @@
{
"collapseWhitespaces": "smart"
}

View File

@ -0,0 +1,6 @@
<!doctype html>
<template id="template">
<slot name="a"><p>a</p></slot>
<slot name="b"><span>test</span> <span>test</span></slot>
<slot name="C"><div>test</div> <div>test</div></slot>
</template>

View File

@ -0,0 +1 @@
<!doctype html><template id=template> <slot name=a><p>a</p></slot> <slot name=b><span>test</span> <span>test</span></slot> <slot name=C><div>test</div><div>test</div></slot> </template>

View File

@ -0,0 +1,3 @@
{
"collapseWhitespaces": "smart"
}

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>element-details - web component using &lt;template&gt; and &lt;slot&gt;</title>
<style>
dl { margin-left: 6px; }
dt { font-weight: bold; color: #217ac0; font-size: 110% }
dt { font-family: Consolas, "Liberation Mono", Courier }
dd { margin-left: 16px }
</style>
</head>
<body>
<h1>element-details - web component using <code>&lt;template&gt;</code> and <code>&lt;slot&gt;</code></h1>
<template id="element-details-template-1">
VALUE: !<slot>?</slot>!
</template>
<element-details>
<span>test</span> <span>foo</span>
</element-details>
<script>
customElements.define('element-details',
class extends HTMLElement {
constructor() {
super();
const template = document
.getElementById('element-details-template-1')
.content;
const shadowRoot = this.attachShadow({mode: 'open'})
.appendChild(template.cloneNode(true));
}
});
</script>
</body>
</html>

View File

@ -0,0 +1 @@
<!doctype html><meta charset=utf-8><title>element-details - web component using &lt;template> and &lt;slot></title><style>dl{margin-left:6px}dt{font-weight:700;color:#217ac0;font-size:110%}dt{font-family:Consolas,"Liberation Mono",Courier}dd{margin-left:16px}</style><h1>element-details - web component using <code>&lt;template></code> and <code>&lt;slot></code></h1><template id=element-details-template-1> VALUE: !<slot>?</slot>! </template> <element-details> <span>test</span> <span>foo</span> </element-details> <script>customElements.define("element-details",class extends HTMLElement{constructor(){super();let a=document.getElementById("element-details-template-1").content,b=this.attachShadow({mode:"open"}).appendChild(a.cloneNode(true))}})</script>

View File

@ -0,0 +1,3 @@
{
"collapseWhitespaces": "smart"
}

View File

@ -0,0 +1,2 @@
<!doctype html>
<span>test</span> <svg><text x="0" y="150" class="small">Мой</text> </svg> <span>a</span>

View File

@ -0,0 +1 @@
<!doctype html><span>test</span> <svg><text x=0 y=150 class=small>Мой</text></svg><span>a</span>

View File

@ -0,0 +1,3 @@
{
"collapseWhitespaces": "smart"
}

View File

@ -0,0 +1,2 @@
<!doctype html>
<span>test</span><svg> a <text x="0" y="150" class="small">Мой</text></svg><span>a</span>

View File

@ -0,0 +1 @@
<!doctype html><span>test</span><svg> a<text x=0 y=150 class=small>Мой</text></svg><span>a</span>

View File

@ -0,0 +1,3 @@
{
"collapseWhitespaces": "smart"
}

View File

@ -0,0 +1,13 @@
<!doctype html>
<svg width="350" height="60" xmlns="http://www.w3.org/2000/svg">
<text>
This is <tspan font-weight="bold" fill="red">bold and red</tspan> <tspan font-weight="bold" fill="red">bold and red</tspan>
</text>
<style><![CDATA[
text{
dominant-baseline: hanging;
font: 28px Verdana, Helvetica, Arial, sans-serif;
}
]]></style>
</svg>

View File

@ -0,0 +1 @@
<!doctype html><svg width=350 height=60><text>This is <tspan font-weight=bold fill=red>bold and red</tspan> <tspan font-weight=bold fill=red>bold and red</tspan></text><style>text{dominant-baseline:hanging;font:28px Verdana,Helvetica,Arial,sans-serif}</style></svg>

View File

@ -0,0 +1,3 @@
{
"collapseWhitespaces": "smart"
}

View File

@ -0,0 +1,96 @@
<!doctype html>
<svg>
<a href=""> </a>
<altGlyph> </altGlyph>
<altGlyphDef> </altGlyphDef>
<altGlyphItem> </altGlyphItem>
<animate> </animate>
<animateColor> </animateColor>
<animateMotion> </animateMotion>
<animateTransform> </animateTransform>
<animation> </animation>
<audio src=""></audio>
<canvas> </canvas>
<circle> </circle>
<clipPath> </clipPath>
<color-profile> </color-profile>
<cursor> </cursor>
<defs> </defs>
<desc> </desc>
<discard> </discard>
<ellipse> </ellipse>
<feBlend> </feBlend>
<feColorMatrix> </feColorMatrix>
<feComponentTransfer> </feComponentTransfer>
<feComposite> </feComposite>
<feConvolveMatrix> </feConvolveMatrix>
<feDiffuseLighting> </feDiffuseLighting>
<feDisplacementMap> </feDisplacementMap>
<feDistantLight> </feDistantLight>
<feDropShadow> </feDropShadow>
<feFlood> </feFlood>
<feFuncA> </feFuncA>
<feFuncB> </feFuncB>
<feFuncG> </feFuncG>
<feFuncR> </feFuncR>
<feGaussianBlur> </feGaussianBlur>
<feImage> </feImage>
<feMerge> </feMerge>
<feMergeNode> </feMergeNode>
<feMorphology> </feMorphology>
<feOffset> </feOffset>
<fePointLight> </fePointLight>
<feSpecularLighting> </feSpecularLighting>
<feSpotLight> </feSpotLight>
<feTile> </feTile>
<feTurbulence> </feTurbulence>
<filter> </filter>
<font> </font>
<font-face> </font-face>
<font-face-format> </font-face-format>
<font-face-name> </font-face-name>
<font-face-src> </font-face-src>
<font-face-uri> </font-face-uri>
<foreignObject> </foreignObject>
<g> </g>
<glyph> </glyph>
<glyphRef> </glyphRef>
<handler> </handler>
<hkern> </hkern>
<iframe src="" frameborder="0"></iframe>
<image> </image>
<line> </line>
<linearGradient> </linearGradient>
<listener> </listener>
<marker> </marker>
<mask> </mask>
<metadata> </metadata>
<missing-glyph> </missing-glyph>
<mpath> </mpath>
<path> </path>
<pattern> </pattern>
<polygon> </polygon>
<polyline> </polyline>
<prefetch> </prefetch>
<radialGradient> </radialGradient>
<rect> </rect>
<script> </script>
<set> </set>
<solidColor> </solidColor>
<stop> </stop>
<style> </style>
<svg> </svg>
<switch> </switch>
<symbol> </symbol>
<tbreak> </tbreak>
<text> </text>
<textArea> </textArea>
<textPath> </textPath>
<title> </title>
<tref> </tref>
<tspan> </tspan>
<unknown> </unknown>
<use> </use>
<video src=""></video>
<view> </view>
</svg>

View File

@ -0,0 +1 @@
<!doctype html><svg><a href="" /><altGlyph/><altGlyphDef/><altGlyphItem/><animate/><animateColor/><animateMotion/><animateTransform/><animation/><audio src="" /> <canvas/><circle> </circle><clipPath/><color-profile/><cursor/><defs/><desc/><discard/><ellipse> </ellipse><feBlend/><feColorMatrix/><feComponentTransfer/><feComposite/><feConvolveMatrix/><feDiffuseLighting/><feDisplacementMap/><feDistantLight/><feDropShadow/><feFlood/><feFuncA/><feFuncB/><feFuncG/><feFuncR/><feGaussianBlur/><feImage/><feMerge/><feMergeNode/><feMorphology/><feOffset/><fePointLight/><feSpecularLighting/><feSpotLight/><feTile/><feTurbulence/><filter/><font/><font-face/><font-face-format/><font-face-name/><font-face-src/><font-face-uri/><foreignObject/><g> </g><glyph/><glyphRef/><handler/><hkern/><iframe src="" frameborder=0 /> <image/><line> </line><linearGradient/><listener/><marker/><mask/><metadata/><missing-glyph/><mpath/><path> </path><pattern/><polygon> </polygon><polyline> </polyline><prefetch/><radialGradient/><rect> </rect><set/><solidcolor/><stop/><svg> </svg><switch> </switch><symbol> </symbol><tbreak/><text/><textarea/><textPath/><title/><tref/><tspan> </tspan><unknown/><use> </use><video src="" /> <view/></svg>

View File

@ -0,0 +1,3 @@
{
"collapseWhitespaces": "smart"
}

View File

@ -0,0 +1,2 @@
<!doctype html>
<span>test</span> <svg> <text x="0" y="150" class="small">Мой</text> </svg> <span>a</span>

View File

@ -0,0 +1 @@
<!doctype html><span>test</span> <svg><text x=0 y=150 class=small>Мой</text></svg><span>a</span>