From afdd79b29d89ee1de484ff4ca1ad119518c32d7c Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Mon, 26 Oct 2026 11:00:26 +0100 Subject: [PATCH] fix: keep a mail full of bare blockquotes from freezing the reader --- lib/__tests__/quote-collapse.test.ts | 19 +++++++++++++++++- lib/quote-collapse.ts | 29 +++++++++++++--------------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/lib/__tests__/quote-collapse.test.ts b/lib/__tests__/quote-collapse.test.ts index 3ca03e596..fb7c18591 100644 --- a/lib/__tests__/quote-collapse.test.ts +++ b/lib/__tests__/quote-collapse.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, beforeEach } from 'vitest'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; import { setupQuoteCollapse, collapsePlainTextQuotes, QUOTE_TOGGLE_ATTR, QUOTE_COLLAPSED_ATTR } from '@/lib/quote-collapse'; import { plainTextToSafeHtml, sanitizeEmailHtml, sanitizePlainTextRenderedHtml } from '@/lib/email-sanitization'; @@ -162,6 +162,23 @@ describe('setupQuoteCollapse', () => { expect(toggle()).toBeNull(); }); + it('looks for an attribution in one pass over many bare blockquotes', () => { + // Every outermost blockquote used to walk all text before it again, so a + // body of a few ten thousand blockquotes froze the reader for minutes. + const n = 2000; + document.body.innerHTML = '
Intro
' + 'x'.repeat(n) + '
Earlier:
late quote'; + const nodes = document.body.querySelectorAll('*').length + n + 3; + const walkerSpy = vi.spyOn(Node.prototype, 'compareDocumentPosition'); + try { + expect(setupQuoteCollapse(document, labels)).toBe(true); + expect(walkerSpy.mock.calls.length).toBeLessThan(nodes * 6); + } finally { + walkerSpy.mockRestore(); + } + expect(document.body.lastElementChild!.textContent).toBe('late quote'); + expect((document.body.lastElementChild as HTMLElement).style.display).toBe('none'); + }); + it('collapses a nested wrapper case: separator inside a wrapper div hides content outside it too', () => { document.body.innerHTML = `