fix(html): Fix bugs of parser and codegen (#4461)

This commit is contained in:
Alexander Akait 2022-04-28 20:57:05 +03:00 committed by GitHub
parent 44567bb065
commit 8bdfcd996a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 585 additions and 32 deletions

View File

@ -0,0 +1,50 @@
use std::ops::{Deref, DerefMut};
use crate::{writer::HtmlWriter, CodeGenerator};
impl<W> CodeGenerator<W>
where
W: HtmlWriter,
{
/// Original context is restored when returned guard is dropped.
#[inline]
pub(super) fn with_ctx(&mut self, ctx: Ctx) -> WithCtx<W> {
let orig_ctx = self.ctx;
self.ctx = ctx;
WithCtx {
orig_ctx,
inner: self,
}
}
}
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct Ctx {
pub skip_escape_text: bool,
}
pub(super) struct WithCtx<'w, I: 'w + HtmlWriter> {
inner: &'w mut CodeGenerator<I>,
orig_ctx: Ctx,
}
impl<'w, I: HtmlWriter> Deref for WithCtx<'w, I> {
type Target = CodeGenerator<I>;
fn deref(&self) -> &CodeGenerator<I> {
self.inner
}
}
impl<'w, I: HtmlWriter> DerefMut for WithCtx<'w, I> {
fn deref_mut(&mut self) -> &mut CodeGenerator<I> {
self.inner
}
}
impl<'w, I: HtmlWriter> Drop for WithCtx<'w, I> {
fn drop(&mut self) {
self.inner.ctx = self.orig_ctx;
}
}

View File

@ -9,10 +9,11 @@ use swc_html_codegen_macros::emitter;
use writer::HtmlWriter;
pub use self::emit::*;
use self::list::ListFormat;
use self::{ctx::Ctx, list::ListFormat};
#[macro_use]
mod macros;
mod ctx;
mod emit;
mod list;
pub mod writer;
@ -28,6 +29,7 @@ where
{
wr: W,
config: CodegenConfig,
ctx: Ctx,
}
impl<W> CodeGenerator<W>
@ -35,7 +37,11 @@ where
W: HtmlWriter,
{
pub fn new(wr: W, config: CodegenConfig) -> Self {
CodeGenerator { wr, config }
CodeGenerator {
wr,
config,
ctx: Default::default(),
}
}
#[emitter]
@ -152,7 +158,26 @@ where
}
if !n.children.is_empty() {
self.emit_list(&n.children, ListFormat::NotDelimited)?;
let skip_escape_text = matches!(
&*n.tag_name,
"style"
| "script"
| "xmp"
| "iframe"
| "noembed"
| "noframes"
| "plaintext"
// TODO we need option here
| "noscript"
);
let ctx = Ctx {
skip_escape_text,
..self.ctx
};
self.with_ctx(ctx)
.emit_list(&n.children, ListFormat::NotDelimited)?;
}
write_raw!(self, "<");
@ -193,25 +218,29 @@ where
#[emitter]
fn emit_text(&mut self, n: &Text) -> Result {
let mut text = String::new();
if self.ctx.skip_escape_text {
write_str!(self, n.span, &n.value);
} else {
let mut text = String::new();
for c in n.value.chars() {
match c {
'&' => {
text.push_str(&String::from("&amp;"));
for c in n.value.chars() {
match c {
'&' => {
text.push_str(&String::from("&amp;"));
}
'<' => {
text.push_str(&String::from("&lt;"));
}
'>' => {
text.push_str(&String::from("&gt;"));
}
'\u{00A0}' => text.push_str(&String::from("&nbsp;")),
_ => text.push(c),
}
'<' => {
text.push_str(&String::from("&lt;"));
}
'>' => {
text.push_str(&String::from("&gt;"));
}
'\u{00A0}' => text.push_str(&String::from("&nbsp;")),
_ => text.push(c),
}
}
write_str!(self, n.span, &text);
write_str!(self, n.span, &text);
}
}
#[emitter]

View File

@ -7,4 +7,10 @@
<script type="module">
console.log();
</script>
<script>window.jQuery || document.write('<script src="jquery.js"><\/script>')</script>
<script>window.jQuery || document.write('<script src="jquery.js"><\/script>')</script>
<script type="text/html">
<div>
test
</div>
<!-- aa -->\n
</script>

View File

@ -7,4 +7,10 @@
<script type=module>
console.log();
</script>
<script>window.jQuery || document.write('&lt;script src="jquery.js"&gt;&lt;\/script&gt;')</script></head><body></body></html>
<script>window.jQuery || document.write('<script src="jquery.js"><\/script>')</script>
<script type=text/html>
<div>
test
</div>
<!-- aa -->\n
</script></head><body></body></html>

View File

@ -3,5 +3,5 @@
</head>
<body>
<textarea name=test id=test></textarea>
&lt;/&gt;
&lt;/&gt;</body></html>
&lt;/body&gt;
&lt;/html&gt;</body></html>

View File

@ -23,16 +23,16 @@
<div>baz</div>
<script>&lt;!-- alert(1) --&gt;</script>
<script>&lt;!--
<script><!-- alert(1) --></script>
<script><!--
alert(1);
--&gt;</script>
<style>&lt;!-- p { color: red } --&gt;</style>
<script>/*&lt;![CDATA[*/alert(8)/*]]&gt;*/</script>
--></script>
<style><!-- p { color: red } --></style>
<script>/*<![CDATA[*/alert(8)/*]]>*/</script>
<script> /*
&lt;![CDATA[ */
<![CDATA[ */
alert(10)
/* ]]&gt; */
/* ]]> */
</script>
</body></html>

View File

@ -353,6 +353,17 @@ where
}
}
fn emit_temporary_buffer(&mut self) {
if let Some(temporary_buffer) = self.temporary_buffer.take() {
for c in temporary_buffer.chars() {
self.emit_token(Token::Character {
value: c,
raw: None,
});
}
}
}
fn read_token_and_span(&mut self) -> LexResult<TokenAndSpan> {
if self.finished {
return Err(ErrorKind::Eof);
@ -885,6 +896,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::Rcdata;
self.reconsume();
}
@ -905,6 +917,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::Rcdata;
self.reconsume();
}
@ -926,6 +939,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::Rcdata;
self.reconsume();
}
@ -1011,6 +1025,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::Rcdata;
self.reconsume();
}
@ -1100,6 +1115,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::Rawtext;
self.reconsume()
}
@ -1120,6 +1136,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::Rawtext;
self.reconsume()
}
@ -1141,6 +1158,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::Rawtext;
self.reconsume()
}
@ -1226,6 +1244,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::Rawtext;
self.reconsume()
}
@ -1329,6 +1348,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::ScriptData;
self.reconsume()
}
@ -1349,6 +1369,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::ScriptData;
self.reconsume()
}
@ -1370,6 +1391,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::ScriptData;
self.reconsume()
}
@ -1455,6 +1477,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::ScriptData;
self.reconsume()
}
@ -1759,6 +1782,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::ScriptDataEscaped;
self.reconsume()
}
@ -1779,6 +1803,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::ScriptDataEscaped;
self.reconsume()
}
@ -1800,6 +1825,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::ScriptDataEscaped;
self.reconsume()
}
@ -1885,6 +1911,7 @@ where
value: '/',
raw: None,
});
self.emit_temporary_buffer();
self.state = State::ScriptDataEscaped;
self.reconsume()
}

View File

@ -543,7 +543,7 @@ where
// namespace
//
// Acknowledge the token's self-closing flag, and then act as
// described in the steps for a "script" end tag below.
// described in the steps for a "script" end tag below.
//
// Otherwise
// Pop the current node off the stack of open elements and acknowledge the token's

View File

@ -215,7 +215,7 @@
"end": 225,
"ctxt": 0
},
"value": "\n <h1>Alternative content</1>\n "
"value": "\n <h1>Alternative content</h1>\n "
}
],
"content": null

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<script type="text/html">
<div>
test
</div>
<!-- aa -->\n
</script>
</body>
</html>

View File

@ -0,0 +1,181 @@
{
"type": "Document",
"span": {
"start": 0,
"end": 210,
"ctxt": 0
},
"mode": "no-quirks",
"children": [
{
"type": "DocumentType",
"span": {
"start": 0,
"end": 15,
"ctxt": 0
},
"name": "html",
"publicId": null,
"systemId": null
},
{
"type": "Element",
"span": {
"start": 16,
"end": 203,
"ctxt": 0
},
"tagName": "html",
"namespace": "http://www.w3.org/1999/xhtml",
"attributes": [
{
"type": "Attribute",
"span": {
"start": 0,
"end": 0,
"ctxt": 0
},
"namespace": null,
"prefix": null,
"name": "lang",
"value": "en"
}
],
"children": [
{
"type": "Element",
"span": {
"start": 33,
"end": 68,
"ctxt": 0
},
"tagName": "head",
"namespace": "http://www.w3.org/1999/xhtml",
"attributes": [],
"children": [
{
"type": "Text",
"span": {
"start": 39,
"end": 44,
"ctxt": 0
},
"value": "\n "
},
{
"type": "Element",
"span": {
"start": 44,
"end": 59,
"ctxt": 0
},
"tagName": "title",
"namespace": "http://www.w3.org/1999/xhtml",
"attributes": [],
"children": [
{
"type": "Text",
"span": {
"start": 51,
"end": 59,
"ctxt": 0
},
"value": "Document"
}
],
"content": null
},
{
"type": "Text",
"span": {
"start": 67,
"end": 68,
"ctxt": 0
},
"value": "\n"
}
],
"content": null
},
{
"type": "Text",
"span": {
"start": 75,
"end": 76,
"ctxt": 0
},
"value": "\n"
},
{
"type": "Element",
"span": {
"start": 76,
"end": 203,
"ctxt": 0
},
"tagName": "body",
"namespace": "http://www.w3.org/1999/xhtml",
"attributes": [],
"children": [
{
"type": "Text",
"span": {
"start": 82,
"end": 87,
"ctxt": 0
},
"value": "\n "
},
{
"type": "Element",
"span": {
"start": 87,
"end": 185,
"ctxt": 0
},
"tagName": "script",
"namespace": "http://www.w3.org/1999/xhtml",
"attributes": [
{
"type": "Attribute",
"span": {
"start": 0,
"end": 0,
"ctxt": 0
},
"namespace": null,
"prefix": null,
"name": "type",
"value": "text/html"
}
],
"children": [
{
"type": "Text",
"span": {
"start": 112,
"end": 185,
"ctxt": 0
},
"value": "\n <div>\n test\n </div>\n <!-- aa -->\\n\n "
}
],
"content": null
},
{
"type": "Text",
"span": {
"start": 194,
"end": 203,
"ctxt": 0
},
"value": "\n\n"
}
],
"content": null
}
],
"content": null
}
]
}

View File

@ -0,0 +1,240 @@
x Document
,-[$DIR/tests/fixture/tag/script/input.html:1:1]
1 | ,-> <!doctype html>
2 | | <html lang="en">
3 | | <head>
4 | | <title>Document</title>
5 | | </head>
6 | | <body>
7 | | <script type="text/html">
8 | | <div>
9 | | test
10 | | </div>
11 | | <!-- aa -->\n
12 | | </script>
13 | | </body>
14 | `-> </html>
`----
x Child
,-[$DIR/tests/fixture/tag/script/input.html:1:1]
1 | <!doctype html>
: ^^^^^^^^^^^^^^^
`----
x DocumentType
,-[$DIR/tests/fixture/tag/script/input.html:1:1]
1 | <!doctype html>
: ^^^^^^^^^^^^^^^
`----
x Child
,-[$DIR/tests/fixture/tag/script/input.html:2:1]
2 | ,-> <html lang="en">
3 | | <head>
4 | | <title>Document</title>
5 | | </head>
6 | | <body>
7 | | <script type="text/html">
8 | | <div>
9 | | test
10 | | </div>
11 | | <!-- aa -->\n
12 | | </script>
13 | `-> </body>
14 | </html>
`----
x Element
,-[$DIR/tests/fixture/tag/script/input.html:2:1]
2 | ,-> <html lang="en">
3 | | <head>
4 | | <title>Document</title>
5 | | </head>
6 | | <body>
7 | | <script type="text/html">
8 | | <div>
9 | | test
10 | | </div>
11 | | <!-- aa -->\n
12 | | </script>
13 | `-> </body>
14 | </html>
`----
x Attribute
,-[$DIR/tests/fixture/tag/script/input.html:1:1]
1 | <!doctype html>
: ^
`----
x Child
,-[$DIR/tests/fixture/tag/script/input.html:3:1]
3 | ,-> <head>
4 | `-> <title>Document</title>
5 | </head>
`----
x Element
,-[$DIR/tests/fixture/tag/script/input.html:3:1]
3 | ,-> <head>
4 | `-> <title>Document</title>
5 | </head>
`----
x Child
,-[$DIR/tests/fixture/tag/script/input.html:3:1]
3 | ,-> <head>
4 | `-> <title>Document</title>
`----
x Text
,-[$DIR/tests/fixture/tag/script/input.html:3:1]
3 | ,-> <head>
4 | `-> <title>Document</title>
`----
x Child
,-[$DIR/tests/fixture/tag/script/input.html:4:5]
4 | <title>Document</title>
: ^^^^^^^^^^^^^^^
`----
x Element
,-[$DIR/tests/fixture/tag/script/input.html:4:5]
4 | <title>Document</title>
: ^^^^^^^^^^^^^^^
`----
x Child
,-[$DIR/tests/fixture/tag/script/input.html:4:5]
4 | <title>Document</title>
: ^^^^^^^^
`----
x Text
,-[$DIR/tests/fixture/tag/script/input.html:4:5]
4 | <title>Document</title>
: ^^^^^^^^
`----
x Child
,-[$DIR/tests/fixture/tag/script/input.html:4:5]
4 | <title>Document</title>
: ^
5 | </head>
`----
x Text
,-[$DIR/tests/fixture/tag/script/input.html:4:5]
4 | <title>Document</title>
: ^
5 | </head>
`----
x Child
,-[$DIR/tests/fixture/tag/script/input.html:5:1]
5 | </head>
: ^
6 | <body>
`----
x Text
,-[$DIR/tests/fixture/tag/script/input.html:5:1]
5 | </head>
: ^
6 | <body>
`----
x Child
,-[$DIR/tests/fixture/tag/script/input.html:6:1]
6 | ,-> <body>
7 | | <script type="text/html">
8 | | <div>
9 | | test
10 | | </div>
11 | | <!-- aa -->\n
12 | | </script>
13 | `-> </body>
14 | </html>
`----
x Element
,-[$DIR/tests/fixture/tag/script/input.html:6:1]
6 | ,-> <body>
7 | | <script type="text/html">
8 | | <div>
9 | | test
10 | | </div>
11 | | <!-- aa -->\n
12 | | </script>
13 | `-> </body>
14 | </html>
`----
x Child
,-[$DIR/tests/fixture/tag/script/input.html:6:1]
6 | ,-> <body>
7 | `-> <script type="text/html">
`----
x Text
,-[$DIR/tests/fixture/tag/script/input.html:6:1]
6 | ,-> <body>
7 | `-> <script type="text/html">
`----
x Child
,-[$DIR/tests/fixture/tag/script/input.html:7:5]
7 | ,-> <script type="text/html">
8 | | <div>
9 | | test
10 | | </div>
11 | | <!-- aa -->\n
12 | `-> </script>
`----
x Element
,-[$DIR/tests/fixture/tag/script/input.html:7:5]
7 | ,-> <script type="text/html">
8 | | <div>
9 | | test
10 | | </div>
11 | | <!-- aa -->\n
12 | `-> </script>
`----
x Child
,-[$DIR/tests/fixture/tag/script/input.html:7:5]
7 | ,-> <script type="text/html">
8 | | <div>
9 | | test
10 | | </div>
11 | | <!-- aa -->\n
12 | `-> </script>
`----
x Text
,-[$DIR/tests/fixture/tag/script/input.html:7:5]
7 | ,-> <script type="text/html">
8 | | <div>
9 | | test
10 | | </div>
11 | | <!-- aa -->\n
12 | `-> </script>
`----
x Child
,-[$DIR/tests/fixture/tag/script/input.html:12:5]
12 | ,-> </script>
13 | `-> </body>
14 | </html>
`----
x Text
,-[$DIR/tests/fixture/tag/script/input.html:12:5]
12 | ,-> </script>
13 | `-> </body>
14 | </html>
`----

View File

@ -215,7 +215,7 @@
"end": 225,
"ctxt": 0
},
"value": "\n <h1>Alternative content</1>\n "
"value": "\n <h1>Alternative content</h1>\n "
}
],
"content": null