#include #include #include #include #include "htmlbuilder.h" #include "messageview.h" #include "mimeparser.h" /// MessageView needs a live QWebEngineProfile, so most of it is verified /// manually. What is pinned here is the one thing that silently produced a /// blank pane: whether a document handed to setHtml() actually loads. class TestMessageView : public QObject { Q_OBJECT private slots: void initTestCase(); void documentActuallyLoads(); void threadContentReachesThePage(); void dataUrlSubResourceStillBlocked(); private: QWebEngineView *webViewOf(MessageView *view) const { return view->findChild(); } }; void TestMessageView::initTestCase() { // Registered in main() in the real application; a test binary has its own // entry point and must do the same before any profile exists. QWebEngineUrlScheme cid(QByteArrayLiteral("cid")); cid.setFlags(QWebEngineUrlScheme::SecureScheme | QWebEngineUrlScheme::ContentSecurityPolicyIgnored); QWebEngineUrlScheme::registerScheme(cid); QWebEngineUrlScheme own(QByteArrayLiteral("qtmaildir")); own.setFlags(QWebEngineUrlScheme::SecureScheme); QWebEngineUrlScheme::registerScheme(own); } void TestMessageView::documentActuallyLoads() { // The regression this exists for: acceptNavigationRequest compared the // navigation's URL against documentUrl(), but setHtml() navigates to a // data: URL and applies the base URL only as the document origin. Every // document load was rejected and the pane stayed blank, with no warning // anywhere. MessageView view; QWebEngineView *web = webViewOf(&view); QVERIFY(web); QSignalSpy loaded(web, &QWebEngineView::loadFinished); ParsedMessage message; message.ok = true; message.from = QStringLiteral("Alice "); message.subject = QStringLiteral("Hello"); message.date = QStringLiteral("Mon, 1 Jun 2026 10:00:00 +0000"); message.plainBody = QStringLiteral("body text"); ThreadRenderItem item; item.message = message; item.cidPrefix = QStringLiteral("m0"); item.expanded = true; view.showThread({ item }); QVERIFY2(loaded.wait(15000), "no loadFinished at all: the document was " "never even attempted"); QCOMPARE(loaded.size(), 1); QVERIFY2(loaded.first().at(0).toBool(), "loadFinished reported failure: the navigation was rejected"); } void TestMessageView::threadContentReachesThePage() { // Loading successfully is not the same as showing the message: assert the // body actually made it into the rendered document. MessageView view; QWebEngineView *web = webViewOf(&view); QVERIFY(web); QSignalSpy loaded(web, &QWebEngineView::loadFinished); ParsedMessage message; message.ok = true; message.from = QStringLiteral("Bob "); message.subject = QStringLiteral("Subject line"); message.plainBody = QStringLiteral("distinctive-body-marker"); ThreadRenderItem item; item.message = message; item.cidPrefix = QStringLiteral("m0"); item.expanded = true; view.showThread({ item }); QVERIFY(loaded.wait(15000)); QVERIFY(loaded.first().at(0).toBool()); QString text; bool done = false; web->page()->toPlainText([&](const QString &result) { text = result; done = true; }); QTRY_VERIFY_WITH_TIMEOUT(done, 15000); QVERIFY2(text.contains(QStringLiteral("distinctive-body-marker")), qPrintable(QStringLiteral("rendered text was: '%1'").arg(text))); QVERIFY(text.contains(QStringLiteral("bob@example.org"))); } void TestMessageView::dataUrlSubResourceStillBlocked() { // The main-frame exemption must not extend to sub-resources: a message // body can write and those stay denied. This is the // narrow line between "the document renders" and "the policy has a hole". MessageView view; QWebEngineView *web = webViewOf(&view); QVERIFY(web); QSignalSpy loaded(web, &QWebEngineView::loadFinished); ParsedMessage message; message.ok = true; message.from = QStringLiteral("Mallory "); message.subject = QStringLiteral("Hostile"); // A 1x1 gif as a data: URL, the shape a tracking-adjacent body would use. message.htmlBody = QStringLiteral( "visible-text" "" ""); ThreadRenderItem item; item.message = message; item.cidPrefix = QStringLiteral("m0"); item.expanded = true; view.showThread({ item }); QVERIFY(loaded.wait(15000)); QVERIFY2(loaded.first().at(0).toBool(), "the document itself must still load"); // The document rendered; the blocked sub-resource is what the interceptor // records. Text is present, so this is not a failed load masquerading as // a blocked image. QString text; bool done = false; web->page()->toPlainText([&](const QString &result) { text = result; done = true; }); QTRY_VERIFY_WITH_TIMEOUT(done, 15000); QVERIFY(text.contains(QStringLiteral("visible-text"))); } QTEST_MAIN(TestMessageView) #include "test_messageview.moc"