diff --git a/src/html/tag.rs b/src/html/tag.rs
index 20ad90db..6406785f 100644
--- a/src/html/tag.rs
+++ b/src/html/tag.rs
@@ -105,6 +105,29 @@ macro_rules! tag_is_one_of {
};
}
+impl Tag {
+ /// Whether `name` is one of the [void elements] of the HTML namespace
+ /// (plus the obsolete ones the tree builder still treats as void).
+ ///
+ /// [void elements]: https://html.spec.whatwg.org/multipage/syntax.html#void-elements
+ #[inline]
+ #[must_use]
+ pub(crate) fn is_void_html_element(name: &impl PartialEq) -> bool {
+ // NOTE: fast path for the most commonly used elements
+ if tag_is_one_of!(*name, [Div, A, Span, Li]) {
+ return false;
+ }
+
+ tag_is_one_of!(
+ *name,
+ [
+ Area, Base, Basefont, Bgsound, Br, Col, Embed, Hr, Img, Input, Keygen, Link, Meta,
+ Param, Source, Track, Wbr
+ ]
+ )
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/src/rewritable_units/element.rs b/src/rewritable_units/element.rs
index d9da98b8..743cb9f5 100644
--- a/src/rewritable_units/element.rs
+++ b/src/rewritable_units/element.rs
@@ -5,6 +5,7 @@ use super::{
};
use crate::HandlerResult;
use crate::base::{BytesCow, SourceLocation};
+use crate::html::{LocalNameHash, Namespace, Tag};
use crate::rewriter::{HandlerTypes, LocalHandlerTypes};
use encoding_rs::Encoding;
use std::any::Any;
@@ -128,18 +129,31 @@ impl<'rewriter, 'input_token, H: HandlerTypes> Element<'rewriter, 'input_token,
/// Sets the tag name of the element.
///
- /// The new tag name must be in the same namespace, have the same content model, and be valid in its location.
+ /// The new tag name must be in the same namespace and be valid in its location.
/// Otherwise change of the tag name may cause the resulting document to be parsed in an unexpected way,
/// out of sync with this library.
+ ///
+ /// If the element is a [void element] (``, ` `, ...) and the new name is not, an end tag
+ /// `` is written right after the start tag, so that the renamed element stays empty instead of
+ /// swallowing the siblings that follow it. The element still [can't have content][Self::can_have_content]:
+ /// [`prepend`][Self::prepend], [`append`][Self::append] and [`set_inner_content`][Self::set_inner_content]
+ /// remain no-ops, and the written end tag doesn't run [end tag handlers][Self::end_tag_handlers].
+ ///
+ /// [void element]: https://html.spec.whatwg.org/multipage/syntax.html#void-elements
#[inline]
pub fn set_tag_name(&mut self, name: &str) -> Result<(), TagNameError> {
- let name = self.tag_name_bytes_from_str(name)?;
+ let name_bytes = self.tag_name_bytes_from_str(name)?;
if self.can_have_content {
- self.modified_end_tag_name = Some((*name).into());
+ self.modified_end_tag_name = Some((*name_bytes).into());
+ } else if self.start_tag.namespace() == Namespace::Html {
+ // A self-closing tag in foreign content is closed by its `/>` whatever its name is,
+ // but an HTML void element is closed by its name alone.
+ let emit_end_tag = !Tag::is_void_html_element(&LocalNameHash::from(name));
+ self.start_tag.set_emit_end_tag(emit_end_tag);
}
- self.start_tag.set_name_raw(name);
+ self.start_tag.set_name_raw(name_bytes);
Ok(())
}
@@ -905,8 +919,8 @@ mod tests {
);
assert_eq!(
out,
- "
-
+ "
+
"
);
}
@@ -922,8 +936,8 @@ mod tests {
);
assert_eq!(
out,
- "