Fix highlights not being applied to plaintext messages (#9126)

* Fix highlights not being applied to plaintext messages

* Add percy test for search result highlighting

* Fix percy target

* Update timeline.spec.ts
This commit is contained in:
Michael Telatynski 2022-08-05 08:08:56 +01:00 committed by GitHub
parent a866005bea
commit e864dbfacf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 41 additions and 29 deletions

View file

@ -470,40 +470,38 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
}
let strippedBody: string;
let safeBody: string;
let isDisplayedWithHtml: boolean;
// XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying
// to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which
// are interrupted by HTML tags (not that we did before) - e.g. foo<span/>bar won't get highlighted
// by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either
let safeBody: string; // safe, sanitised HTML, preferred over `strippedBody` which is fully plaintext
try {
if (highlights && highlights.length > 0) {
const highlighter = new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink);
const safeHighlights = highlights
// sanitizeHtml can hang if an unclosed HTML tag is thrown at it
// A search for `<foo` will make the browser crash
// an alternative would be to escape HTML special characters
// but that would bring no additional benefit as the highlighter
// does not work with those special chars
.filter((highlight: string): boolean => !highlight.includes("<"))
.map((highlight: string): string => sanitizeHtml(highlight, sanitizeParams));
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure.
sanitizeParams.textFilter = function(safeText) {
return highlighter.applyHighlights(safeText, safeHighlights).join('');
};
}
// sanitizeHtml can hang if an unclosed HTML tag is thrown at it
// A search for `<foo` will make the browser crash an alternative would be to escape HTML special characters
// but that would bring no additional benefit as the highlighter does not work with those special chars
const safeHighlights = highlights
?.filter((highlight: string): boolean => !highlight.includes("<"))
.map((highlight: string): string => sanitizeHtml(highlight, sanitizeParams));
let formattedBody = typeof content.formatted_body === 'string' ? content.formatted_body : null;
const plainBody = typeof content.body === 'string' ? content.body : "";
if (opts.stripReplyFallback && formattedBody) formattedBody = stripHTMLReply(formattedBody);
strippedBody = opts.stripReplyFallback ? stripPlainReply(plainBody) : plainBody;
bodyHasEmoji = mightContainEmoji(isFormattedBody ? formattedBody : plainBody);
// Only generate safeBody if the message was sent as org.matrix.custom.html
const highlighter = safeHighlights?.length
? new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink)
: null;
if (isFormattedBody) {
isDisplayedWithHtml = true;
if (highlighter) {
// XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying
// to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which
// are interrupted by HTML tags (not that we did before) - e.g. foo<span/>bar won't get highlighted
// by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure.
sanitizeParams.textFilter = function(safeText) {
return highlighter.applyHighlights(safeText, safeHighlights).join('');
};
}
safeBody = sanitizeHtml(formattedBody, sanitizeParams);
const phtml = cheerio.load(safeBody, {
@ -533,12 +531,14 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
if (bodyHasEmoji) {
safeBody = formatEmojis(safeBody, true).join('');
}
} else if (highlighter) {
safeBody = highlighter.applyHighlights(plainBody, safeHighlights).join('');
}
} finally {
delete sanitizeParams.textFilter;
}
const contentBody = isDisplayedWithHtml ? safeBody : strippedBody;
const contentBody = safeBody ?? strippedBody;
if (opts.returnString) {
return contentBody;
}
@ -576,11 +576,11 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
});
let emojiBodyElements: JSX.Element[];
if (!isDisplayedWithHtml && bodyHasEmoji) {
if (!safeBody && bodyHasEmoji) {
emojiBodyElements = formatEmojis(strippedBody, false) as JSX.Element[];
}
return isDisplayedWithHtml ?
return safeBody ?
<span
key="body"
ref={opts.ref}