From 6b2e83cb6331644ac4f725d151aabb11cf3ce869 Mon Sep 17 00:00:00 2001
From: hayaksi1 <193020925+hayaksi1@users.noreply.github.com>
Date: Tue, 25 Aug 2026 11:03:42 +0300
Subject: [PATCH] Fall back to the message body when the formatted body has
nothing to render
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The HTML sanitiser in toHtmlDocument() drops every tag outside its safelist,
which does not include img. A formatted body whose only content is an image —
a custom emoji, or a bot posting an image inline — therefore cleans down to an
empty document, and because that document is not null the ?: body fallbacks in
the timeline item factory, the room list preview, the notification resolver and
plainText are all dead. The message renders as an empty bubble.
Return null from toHtmlDocument() when no text survives the sanitisation, so the
existing fallbacks take the plain text body, which for these messages carries the
alt text the sender provided.
---
.../TimelineItemContentMessageFactoryTest.kt | 19 +++++++++++++++
.../matrix/ui/messages/ToHtmlDocument.kt | 5 ++--
.../matrix/ui/messages/ToHtmlDocumentTest.kt | 24 +++++++++++++++++++
.../matrix/ui/messages/ToPlainTextTest.kt | 12 ++++++++++
4 files changed, 58 insertions(+), 2 deletions(-)
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt
index fc01184764..c014b003d7 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt
@@ -229,6 +229,25 @@ class TimelineItemContentMessageFactoryTest : RobolectricTest() {
assertThat((result as TimelineItemTextContent).formattedBody).isEqualTo(SpannedString("body"))
}
+ @Test
+ fun `test create TextMessageType with a formatted body containing only an image falls back to the body`() = runTest {
+ val sut = createTimelineItemContentMessageFactory()
+ val result = sut.create(
+ content = createMessageContent(
+ type = TextMessageType(
+ body = "\uD83D\uDE1C",
+ formatted = FormattedBody(MessageFormat.HTML, "
")
+ )
+ ),
+ senderId = A_USER_ID,
+ senderProfile = aProfileDetails(),
+ eventId = AN_EVENT_ID,
+ )
+ assertThat((result as TimelineItemTextContent).formattedBody).isEqualTo(SpannedString("\uD83D\uDE1C"))
+ assertThat(result.htmlDocument).isNull()
+ assertThat(result.plainText).isEqualTo("\uD83D\uDE1C")
+ }
+
@Test
fun `test create VideoMessageType`() = runTest {
val sut = createTimelineItemContentMessageFactory()
diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt
index d29a28cba9..2f52320b0d 100644
--- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt
+++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt
@@ -19,7 +19,8 @@ import org.jsoup.safety.Safelist
/**
* Converts the HTML string [FormattedBody.body] to a [Document] by parsing it.
- * If the message is not formatted or the format is not [MessageFormat.HTML] we return `null`.
+ * If the message is not formatted, the format is not [MessageFormat.HTML], or nothing renderable is left once the
+ * unsupported tags have been removed, we return `null` so that the plain text body is used instead.
*
* This will also make sure mentions are prefixed with `@`.
*
@@ -44,7 +45,7 @@ fun FormattedBody.toHtmlDocument(
// Prepend `@` to mentions
fixMentions(dom, permalinkParser)
- dom
+ dom.takeIf { it.text().isNotBlank() }
}
}
diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocumentTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocumentTest.kt
index ae336cd0d0..dcf8935e57 100644
--- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocumentTest.kt
+++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocumentTest.kt
@@ -103,4 +103,28 @@ class ToHtmlDocumentTest : RobolectricTest() {
})
assertThat(document?.text()).isEqualTo("Hey Alice!")
}
+
+ @Test
+ fun `toHtmlDocument - returns null if the body only contains unsupported tags`() {
+ val body = FormattedBody(
+ format = MessageFormat.HTML,
+ body = "
"
+ )
+
+ val document = body.toHtmlDocument(permalinkParser = FakePermalinkParser())
+
+ assertThat(document).isNull()
+ }
+
+ @Test
+ fun `toHtmlDocument - returns a Document if some text survives next to unsupported tags`() {
+ val body = FormattedBody(
+ format = MessageFormat.HTML,
+ body = "Look at this
!"
+ )
+
+ val document = body.toHtmlDocument(permalinkParser = FakePermalinkParser())
+
+ assertThat(document?.text()).isEqualTo("Look at this !")
+ }
}
diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainTextTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainTextTest.kt
index b1031176e7..aa5cf2e96e 100644
--- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainTextTest.kt
+++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainTextTest.kt
@@ -149,4 +149,16 @@ class ToPlainTextTest : RobolectricTest() {
)
assertThat(messageType.toPlainText(permalinkParser = FakePermalinkParser())).isEqualTo("This is the message content.")
}
+
+ @Test
+ fun `TextMessageType toPlainText - returns the body if the formatted one only contains an image`() {
+ val messageType = TextMessageType(
+ body = "\uD83D\uDE1C",
+ formatted = FormattedBody(
+ format = MessageFormat.HTML,
+ body = "
"
+ )
+ )
+ assertThat(messageType.toPlainText(permalinkParser = FakePermalinkParser())).isEqualTo("\uD83D\uDE1C")
+ }
}