fix(fetch): preserve details and discard empty SVG placeholders

This commit is contained in:
kevin
2026-09-20 20:50:03 +08:00
committed by Donough Liu
parent b75e26fca2
commit 7985fcb7bc
4 changed files with 63 additions and 3 deletions
+21 -1
View File
@@ -234,7 +234,20 @@ impl<'a, D: Dom + ?Sized> Machine<'a, D> {
.attribute(node, "src")
.filter(|src| !src.is_empty())
{
self.writer().image(alt, src, title);
// Lazy-image placeholders carry no visible image. Keep
// accessible alternate text without emitting a data URI.
if is_empty_svg_placeholder(src) {
if !alt.is_empty()
&& !self
.dom
.attribute(node, "aria-hidden")
.is_some_and(|value| value.trim().eq_ignore_ascii_case("true"))
{
self.writer().text(alt);
}
} else {
self.writer().image(alt, src, title);
}
}
return;
}
@@ -428,6 +441,13 @@ impl<'a, D: Dom + ?Sized> Machine<'a, D> {
}
}
fn is_empty_svg_placeholder(src: &str) -> bool {
let lower = src.to_ascii_lowercase();
lower.starts_with("data:image/svg+xml,%3csvg")
&& lower.ends_with("%3c/svg%3e")
&& lower.matches("%3c").count() == 2
}
fn class_language(class: Option<&str>) -> Option<&str> {
class?
.split_ascii_whitespace()
+15
View File
@@ -225,6 +225,21 @@ fn images_without_sources_do_not_emit_placeholder_markdown() {
}
}
#[test]
fn empty_svg_lazy_image_placeholders_do_not_emit_data_uris() {
let placeholder = "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20720%20960'%3E%3C/svg%3E";
let html = format!("before<img alt='Recipe photo' src=\"{placeholder}\">after");
assert_eq!(markdown(&html, false), "beforeRecipe photoafter");
let html =
format!("before<img alt='Recipe photo' aria-hidden='true' src=\"{placeholder}\">after");
assert_eq!(markdown(&html, false), "beforeafter");
let real_svg = "data:image/svg+xml,%3Csvg%3E%3Cpath%20d='M0%200'/%3E%3C/svg%3E";
let html = format!("<img alt='Logo' src=\"{real_svg}\">");
assert!(markdown(&html, false).contains(real_svg));
}
#[test]
fn list_paragraphs_and_code_blocks_require_blank_lines_between_items() {
for (html, expected) in [
@@ -157,8 +157,6 @@ fn apply_strip_options(dom: &mut NativeDom, strip: RendererPageDumpStripOptions)
| "dialog"
| "menu"
| "menuitem"
| "details"
| "summary"
));
if should_remove {
remove.push(node_id);
+27
View File
@@ -129,6 +129,33 @@ async fn render_page_dump_default_html_uses_renderer_live_serialize() -> Result<
Ok(())
}
#[tokio::test]
async fn strip_ui_keeps_content_inside_details() -> Result<()> {
let (_browser, mut page, http_server) = load_page(
"<!doctype html><html><body><nav>Menu</nav><main><details><summary>Product details</summary><p>Critical product description</p></details></main></body></html>",
)
.await?;
let rendered = render_page_dump_with_options_async(
&mut page,
DumpFormat::Markdown,
StripOptions {
js: false,
css: false,
ui: true,
},
false,
false,
false,
None,
)
.await?;
assert!(!rendered.contains("Menu"));
assert!(rendered.contains("Product details"));
assert!(rendered.contains("Critical product description"));
http_server.abort();
Ok(())
}
#[tokio::test]
async fn render_page_dump_postprocessed_html_uses_renderer_live_dump() -> Result<()> {
let (_browser, mut page, http_server) = load_page(