Only locate a quote in the text the bubble actually rendered - #3356
Open
FrayxRulez wants to merge 1 commit into
Open
FrayxRulez wants to merge 1 commit into
FrayxRulez wants to merge 1 commit into
Conversation
MessageBubble.Highlight walks message.Text.Paragraphs and asks Message for the block rendering each one, pairing an index into the model with an index into the view. Nothing guaranteed the two describe the same text, and GetBlock returns null when no range covers the index, so the paragraph dereference a few lines later threw. MessageAnimatedEmoji is the deterministic case. The message is a plain text message that TDLib re-types in the API layer, so it is quotable everywhere and the quote rides on the replying message; searchQuote compares text only and GetCaption returns the emoji, so the quote always matches. But UpdateMessageText excludes it from SetText and Recycle clears the ranges, so every GetBlock is null. A translation or a summary reaches the same state differently: it is an independent StyledText, and since entities are dropped in translation a blockquote that forced a paragraph break in the original no longer does, so the rendered text can have fewer paragraphs than the model. Compare the two by reference before entering the branch, rather than guarding the null it produces. The null only appears when the paragraph counts differ; when they happen to match but the content does not, the old code drew the highlight in the wrong places instead of crashing, and that is silent. Falling back to the whole-bubble rect is also the right visual: content that never reaches SetText is drawn by Media, which the fallback already targets. Reported by crash telemetry on 12.9.1. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
NullReferenceExceptionatMessageBubble.xaml.cs:2961, reported by crash telemetry on 12.9.1.Cause
Highlightwalksmessage.Text.Paragraphsand asks the view for the block that renderseach one:
It pairs an index into the model with an index into the view, and nothing guaranteed the two
describe the same text.
MessageTextBlock.GetBlockreturnsnullwhen no range covers theindex, and
paragraph.Margin.Lefta few lines down is the dereference that throws.The assumption is known to be breakable elsewhere —
TextPointer.OffsetToIndexbails out ontextBlock.Blocks.Count != text.Paragraphs.Count, andMessageQuote_Loadedalready refuses tobuild a quote for
MessageRichMessage.Highlightis the onlyGetBlockcaller with no check.MessageAnimatedEmojiis the deterministic case. The underlying message is a plain textmessage — TDLib re-types it as
messageAnimatedEmojiin the API layer and can flip it live viaupdateMessageContent— so it is quotable in every client. The quote rides on the replyingmessage and carries the original text,
searchQuotecompares text only and not entities, andGetCaption()returns the emoji, so the quote always matches at index 0. ButUpdateMessageTextexcludes that content type from
Message.SetText, andRecycle()clears the ranges, so everyGetBlockreturns null.Translation and summary reach the same state differently. Each is an independent
StyledTextfed intoMessagewhileHighlightstill walksmessage.Text. Entities aredropped in translation, so a blockquote that forced a paragraph break in the original no longer
does and the rendered text can have fewer paragraphs than the model.
The fix
Compare the two by reference before entering the quote branch, rather than guarding the null it
produces:
MessageTextBlocknow exposes theStyledTextits blocks were built from, mirroringFormattedTextBlock.Text.Guarding the null instead would have left a silent bug: the null only appears when the paragraph
counts differ. When they happen to match but the content does not — a translation of equal line
count — the old code drew the highlight rectangles in the wrong places rather than crashing.
Falling back to the whole-bubble rect is also the right visual here.
lightis true forMessageAnimatedEmoji, so the fallback targetsMedia, which is what actually drew the content.Nothing upstream can prevent the state: the quote already exists in history and may have been
made from another client, so it cannot be validated away at render time.
Build
Not built — a UWP/.NET Native build isn't available here. Both files pass
CSharpSyntaxTree.ParseText(...).GetDiagnostics(), which confirms they parse and nothing more.🤖 Generated with Claude Code