MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 92.3% 228 / 0 / 247
Functions: 87.5% 14 / 0 / 16
Branches: 85.3% 272 / 0 / 319

service/NativeDocumentService.cpp
Line Branch Exec Source
1 #include "NativeDocumentService.h"
2
3 #include <QCoreApplication>
4 #include <QFutureWatcher>
5 #include <QtConcurrent>
6 #include <QTextDocument>
7 #include <QTextTable>
8 #include <QFontMetricsF>
9 #include <QStandardPaths>
10 #include <QDir>
11 #include <QGuiApplication>
12 #include <QDataStream>
13 #include <algorithm>
14 #include "util/Utf8Digest.h"
15
16 32 NativeDocumentService::NativeDocumentService(QObject *parent, QString cacheDirectory)
17
2/2
✓ Branch 3 → 4 taken 32 times.
✓ Branch 5 → 6 taken 32 times.
32 : QObject(parent), m_cache(std::make_unique<NativeDocumentCache>(std::move(cacheDirectory))) {
18
1/1
✓ Branch 9 → 10 taken 32 times.
32 m_pool.setMaxThreadCount(1);
19
1/1
✓ Branch 10 → 11 taken 32 times.
32 m_pool.setExpiryTimeout(-1);
20
1/1
✓ Branch 12 → 13 taken 32 times.
32 connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this,
21 32 [this] { stop(); });
22 32 }
23
24 42 NativeDocumentService::~NativeDocumentService() {
25 32 stop();
26 42 }
27
28 34 void NativeDocumentService::stop() {
29 34 m_stopping = true;
30
2/2
✓ Branch 3 → 4 taken 4 times.
✓ Branch 3 → 7 taken 30 times.
34 if (m_active) m_active->cancelled->store(true, std::memory_order_relaxed);
31 34 m_pending.clear();
32
1/1
✓ Branch 9 → 10 taken 34 times.
34 m_pool.waitForDone();
33 34 }
34
35 10 NativeDocumentService *NativeDocumentService::shared() {
36
3/4
✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 7 taken 5 times.
✓ Branch 4 → 5 taken 5 times.
✗ Branch 4 → 7 not taken.
10 static QPointer<NativeDocumentService> instance;
37
2/2
✓ Branch 8 → 9 taken 5 times.
✓ Branch 8 → 27 taken 5 times.
20 if (!instance) instance = new NativeDocumentService(QCoreApplication::instance(),
38
7/10
✓ Branch 10 → 11 taken 5 times.
✓ Branch 11 → 12 taken 5 times.
✓ Branch 12 → 13 taken 5 times.
✓ Branch 13 → 14 taken 5 times.
✓ Branch 15 → 16 taken 5 times.
✓ Branch 16 → 17 taken 5 times.
✓ Branch 21 → 22 taken 5 times.
✗ Branch 21 → 23 not taken.
✗ Branch 41 → 42 not taken.
✗ Branch 41 → 43 not taken.
10 QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)).filePath("native-documents-v1"));
39 10 return instance;
40 }
41
42 1190 void NativeDocumentService::cancel(QObject *owner) {
43
6/6
✓ Branch 3 → 4 taken 1046 times.
✓ Branch 3 → 8 taken 144 times.
✓ Branch 6 → 7 taken 1008 times.
✓ Branch 6 → 8 taken 38 times.
✓ Branch 9 → 10 taken 1008 times.
✓ Branch 9 → 13 taken 182 times.
1190 if (m_active && m_active->owner == owner)
44 1008 m_active->cancelled->store(true, std::memory_order_relaxed);
45
2/2
✓ Branch 25 → 14 taken 1160 times.
✓ Branch 25 → 26 taken 1190 times.
2350 for (qsizetype i = m_pending.size(); i > 0; --i)
46
5/6
✓ Branch 16 → 17 taken 1160 times.
✗ Branch 16 → 20 not taken.
✓ Branch 19 → 20 taken 999 times.
✓ Branch 19 → 21 taken 161 times.
✓ Branch 22 → 23 taken 999 times.
✓ Branch 22 → 24 taken 161 times.
1160 if (!m_pending[i - 1].owner || m_pending[i - 1].owner == owner)
47 999 m_pending.removeAt(i - 1);
48 1190 }
49
50 1095 void NativeDocumentService::prepare(QObject *owner, const QByteArray &html, Callback callback,
51 NativeImageContext images, std::optional<QFont> displayFont) {
52
1/1
✓ Branch 2 → 3 taken 1095 times.
1095 cancel(owner);
53
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 1094 times.
1097 if (!owner) return;
54
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 11 taken 1093 times.
1094 if (m_stopping) {
55 1 NativeMailDocument result;
56 1 result.outcome = NativeMailDocument::Outcome::Cancelled;
57
1/1
✓ Branch 7 → 8 taken 1 time.
1 callback(result);
58 1 return;
59 1 }
60
2/2
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 18 taken 1092 times.
1093 if (html.size() > NativeMailDocument::MaxInputBytes) {
61 1 NativeMailDocument result;
62 1 result.outcome = NativeMailDocument::Outcome::Limited;
63
1/1
✓ Branch 14 → 15 taken 1 time.
1 callback(result);
64 1 return;
65 1 }
66
1/1
✓ Branch 18 → 19 taken 1092 times.
1092 Request request;
67
1/1
✓ Branch 19 → 20 taken 1092 times.
1092 request.owner = owner;
68 1092 request.input = html;
69 1092 request.callback = std::move(callback);
70 1092 request.images = std::move(images);
71
2/2
✓ Branch 25 → 26 taken 1092 times.
✓ Branch 26 → 27 taken 1092 times.
1092 request.defaultFont = displayFont.value_or(QGuiApplication::font());
72
1/1
✓ Branch 30 → 31 taken 1092 times.
2184 request.cacheKey = NativeDocumentCache::key(request.images.databasePath,
73 1092 request.images.folderId, request.images.uid, request.images.uidValidity, html);
74
2/2
✓ Branch 34 → 35 taken 1092 times.
✓ Branch 35 → 36 taken 1092 times.
1092 enqueue(std::move(request));
75 1092 }
76
77 26 void NativeDocumentService::prepareDecodedHtml(QObject *owner, const QString &html,
78 Callback callback, NativeImageContext images,
79 std::optional<QFont> displayFont,
80 QByteArray htmlDigest) {
81
1/1
✓ Branch 2 → 3 taken 26 times.
26 cancel(owner);
82
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 26 times.
27 if (!owner) return;
83
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 11 taken 26 times.
26 if (m_stopping) {
84 ✗ NativeMailDocument result;
85 ✗ result.outcome = NativeMailDocument::Outcome::Cancelled;
86 ✗ callback(result);
87 ✗ return;
88 ✗ }
89 // A UTF-16 code unit always contributes at least one UTF-8 byte. Reject
90 // oversized documents before allocating or hashing any encoded chunks.
91
2/2
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 18 taken 25 times.
26 if (html.size() > NativeMailDocument::MaxInputBytes) {
92 1 NativeMailDocument result;
93 1 result.outcome = NativeMailDocument::Outcome::Limited;
94
1/1
✓ Branch 14 → 15 taken 1 time.
1 callback(result);
95 1 return;
96 1 }
97 // Display snapshots carry the exact digest from their read worker.
98 // Callers without that hint keep the bounded local fallback.
99
2/2
✓ Branch 19 → 20 taken 12 times.
✓ Branch 19 → 37 taken 13 times.
25 if (htmlDigest.size() != 64) {
100
1/1
✓ Branch 21 → 22 taken 12 times.
12 const auto digest = MailJD::sha256Utf8HexUpTo(html, NativeMailDocument::MaxInputBytes);
101
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 29 taken 12 times.
12 if (!digest) {
102 ✗ NativeMailDocument result;
103 ✗ result.outcome = NativeMailDocument::Outcome::Limited;
104 ✗ callback(result);
105 ✗ return;
106 ✗ }
107 12 htmlDigest = *digest;
108 12 }
109
1/1
✓ Branch 37 → 38 taken 25 times.
25 Request request;
110
1/1
✓ Branch 38 → 39 taken 25 times.
25 request.owner = owner;
111 25 request.decodedInput = html;
112 25 request.callback = std::move(callback);
113
1/1
✓ Branch 42 → 43 taken 25 times.
25 images.expectedHtml.clear();
114 25 request.images = std::move(images);
115
2/2
✓ Branch 45 → 46 taken 25 times.
✓ Branch 46 → 47 taken 25 times.
25 request.defaultFont = displayFont.value_or(QGuiApplication::font());
116
1/1
✓ Branch 50 → 51 taken 25 times.
50 request.cacheKey = NativeDocumentCache::keyWithDigest(request.images.databasePath,
117 request.images.folderId, request.images.uid, request.images.uidValidity,
118 25 htmlDigest);
119
2/2
✓ Branch 54 → 55 taken 25 times.
✓ Branch 55 → 56 taken 25 times.
25 enqueue(std::move(request));
120 25 }
121
122 1117 void NativeDocumentService::enqueue(Request request) {
123
2/2
✓ Branch 3 → 4 taken 70 times.
✓ Branch 3 → 11 taken 1047 times.
1117 if (!request.cacheKey.isEmpty()) {
124 70 request.preparedKey = request.cacheKey;
125
1/1
✓ Branch 6 → 7 taken 70 times.
70 QDataStream fontStream(&request.preparedKey, QIODevice::Append);
126
2/2
✓ Branch 7 → 8 taken 70 times.
✓ Branch 8 → 9 taken 70 times.
70 fontStream << request.images.loadInlineImages << request.defaultFont;
127 70 }
128
2/2
✓ Branch 13 → 14 taken 13 times.
✓ Branch 13 → 20 taken 1104 times.
1117 if (auto ready = preparedResult(request)) {
129
1/1
✓ Branch 17 → 18 taken 13 times.
13 request.callback(std::move(*ready));
130 13 return;
131 1117 }
132
1/1
✓ Branch 25 → 27 taken 1104 times.
1104 m_pending.append(std::move(request));
133
1/1
✓ Branch 27 → 28 taken 1104 times.
1104 QPointer<NativeDocumentService> self(this);
134
2/2
✓ Branch 29 → 30 taken 13 times.
✓ Branch 29 → 40 taken 1091 times.
1104 if (m_pending.size() > 8) {
135
1/1
✓ Branch 30 → 31 taken 13 times.
13 auto dropped = m_pending.takeFirst();
136 13 NativeMailDocument result;
137 13 result.outcome = NativeMailDocument::Outcome::Cancelled;
138
2/3
✓ Branch 32 → 33 taken 13 times.
✗ Branch 32 → 37 not taken.
✓ Branch 34 → 35 taken 13 times.
13 if (dropped.owner) dropped.callback(result);
139 13 }
140
3/3
✓ Branch 41 → 42 taken 1103 times.
✓ Branch 41 → 43 taken 1 time.
✓ Branch 42 → 43 taken 1103 times.
1104 if (self) startNext();
141 1104 }
142
143 1200 std::optional<NativeMailDocument> NativeDocumentService::preparedResult(const Request &request) {
144
2/2
✓ Branch 3 → 4 taken 1073 times.
✓ Branch 3 → 5 taken 127 times.
1200 if (request.preparedKey.isEmpty()) return {};
145
2/2
✓ Branch 6 → 7 taken 16 times.
✓ Branch 6 → 11 taken 111 times.
127 if (auto *ready = m_prepared.object(request.preparedKey)) {
146 16 auto result = *ready;
147 16 result.cacheHit = true;
148 16 return result;
149 16 }
150 111 return {};
151 }
152
153 1179 void NativeDocumentService::startNext() {
154
6/6
✓ Branch 2 → 3 taken 1178 times.
✓ Branch 2 → 5 taken 1 time.
✓ Branch 4 → 5 taken 1035 times.
✓ Branch 4 → 6 taken 143 times.
✓ Branch 7 → 8 taken 1036 times.
✓ Branch 7 → 9 taken 143 times.
1242 if (m_stopping || m_active) return;
155
7/8
✓ Branch 50 → 51 taken 144 times.
✓ Branch 50 → 56 taken 1 time.
✓ Branch 52 → 53 taken 144 times.
✗ Branch 52 → 56 not taken.
✓ Branch 54 → 55 taken 84 times.
✓ Branch 54 → 56 taken 60 times.
✓ Branch 57 → 10 taken 84 times.
✓ Branch 57 → 58 taken 61 times.
145 while (!m_stopping && !m_active && !m_pending.isEmpty()) {
156
1/1
✓ Branch 10 → 11 taken 84 times.
84 auto request = m_pending.takeFirst();
157
2/2
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 14 taken 83 times.
84 if (!request.owner) continue;
158 // A preceding request may have prepared this same document while it
159 // waited. Do not send it through the worker queue again.
160
2/2
✓ Branch 16 → 17 taken 3 times.
✓ Branch 16 → 36 taken 80 times.
83 if (auto ready = preparedResult(request)) {
161
1/1
✓ Branch 17 → 18 taken 3 times.
3 QPointer<NativeDocumentService> self(this);
162
1/1
✓ Branch 21 → 22 taken 3 times.
3 request.callback(std::move(*ready));
163
6/6
✓ Branch 24 → 25 taken 2 times.
✓ Branch 24 → 27 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
✓ Branch 26 → 28 taken 1 time.
✓ Branch 29 → 30 taken 2 times.
✓ Branch 29 → 31 taken 1 time.
3 if (!self || m_active) return;
164 1 continue;
165 86 }
166
1/1
✓ Branch 41 → 44 taken 80 times.
80 m_active = std::move(request);
167 80 break;
168 84 }
169
6/6
✓ Branch 58 → 59 taken 140 times.
✓ Branch 58 → 61 taken 1 time.
✓ Branch 60 → 61 taken 60 times.
✓ Branch 60 → 62 taken 80 times.
✓ Branch 63 → 64 taken 61 times.
✓ Branch 63 → 65 taken 80 times.
141 if (m_stopping || !m_active) return;
170
3/6
✓ Branch 65 → 66 taken 80 times.
✓ Branch 66 → 67 taken 80 times.
✗ Branch 67 → 68 not taken.
✓ Branch 67 → 69 taken 80 times.
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 117 not taken.
80 auto *watcher = new QFutureWatcher<NativeMailDocument>(this);
171
1/1
✓ Branch 69 → 70 taken 80 times.
80 connect(watcher, &QFutureWatcher<NativeMailDocument>::finished, this, [this, watcher] {
172
1/1
✓ Branch 4 → 5 taken 77 times.
77 auto request = std::move(*m_active);
173 77 m_active.reset();
174 // The prepared fragment and decoded image map have one consumer. Move
175 // them out of the completed future before invoking the reader callback.
176
2/2
✓ Branch 6 → 7 taken 77 times.
✓ Branch 7 → 8 taken 77 times.
77 auto result = watcher->future().takeResult();
177
1/1
✓ Branch 9 → 10 taken 77 times.
77 watcher->deleteLater();
178
6/6
✓ Branch 11 → 12 taken 74 times.
✓ Branch 11 → 16 taken 3 times.
✓ Branch 14 → 15 taken 70 times.
✓ Branch 14 → 16 taken 4 times.
✓ Branch 17 → 18 taken 70 times.
✓ Branch 17 → 69 taken 7 times.
77 if (request.owner && !request.cancelled->load(std::memory_order_relaxed)) {
179
1/1
✓ Branch 20 → 21 taken 70 times.
70 const bool hasInlineReferences = std::any_of(
180 result.imageReferences.cbegin(), result.imageReferences.cend(),
181 12 [](const NativeMailDocument::ImageReference &reference) {
182 12 return !reference.contentId.isEmpty();
183 });
184
5/6
✓ Branch 22 → 23 taken 51 times.
✓ Branch 22 → 29 taken 19 times.
✓ Branch 23 → 24 taken 51 times.
✗ Branch 23 → 29 not taken.
✓ Branch 30 → 31 taken 46 times.
✓ Branch 30 → 55 taken 24 times.
121 if (!request.preparedKey.isEmpty() && result.outcome == NativeMailDocument::Outcome::Ready &&
185
5/6
✓ Branch 24 → 25 taken 45 times.
✓ Branch 24 → 28 taken 6 times.
✓ Branch 26 → 27 taken 45 times.
✗ Branch 26 → 29 not taken.
✓ Branch 27 → 28 taken 40 times.
✓ Branch 27 → 29 taken 5 times.
51 (!request.images.loadInlineImages || (result.fragment && !hasInlineReferences))) {
186 46 qsizetype charge = result.html.size() * 16 + result.plainText.size() * 2
187 46 + qsizetype(qMax(0, result.nodes)) * 256 + 4096;
188
4/4
✓ Branch 34 → 35 taken 46 times.
✓ Branch 35 → 36 taken 46 times.
✓ Branch 46 → 37 taken 4 times.
✓ Branch 46 → 47 taken 46 times.
50 for (const auto &reference : result.imageReferences)
189 4 charge += 128 + 2 * (reference.placeholder.size() + reference.contentId.size()
190
1/1
✓ Branch 41 → 42 taken 4 times.
4 + reference.remoteUrl.toString().size());
191
2/2
✓ Branch 48 → 49 taken 45 times.
✓ Branch 48 → 55 taken 1 time.
46 if (charge <= m_prepared.maxCost())
192
3/6
✓ Branch 49 → 50 taken 45 times.
✓ Branch 51 → 52 taken 45 times.
✗ Branch 52 → 53 not taken.
✓ Branch 52 → 54 taken 45 times.
✗ Branch 87 → 88 not taken.
✗ Branch 87 → 89 not taken.
45 m_prepared.insert(request.preparedKey, new NativeMailDocument(result), int(charge));
193 }
194
1/1
✓ Branch 55 → 56 taken 70 times.
70 QPointer<NativeDocumentService> self(this);
195
1/1
✓ Branch 58 → 59 taken 70 times.
70 request.callback(std::move(result));
196
2/2
✓ Branch 61 → 62 taken 1 time.
✓ Branch 61 → 63 taken 69 times.
70 if (!self) return;
197 70 }
198
1/1
✓ Branch 69 → 70 taken 76 times.
76 startNext();
199
4/4
✓ Branch 72 → 73 taken 76 times.
✓ Branch 72 → 74 taken 1 time.
✓ Branch 76 → 77 taken 76 times.
✓ Branch 76 → 79 taken 1 time.
78 });
200 80 const QByteArray input = m_active->input;
201 80 const QString decodedInput = m_active->decodedInput;
202 80 const auto cancelled = m_active->cancelled;
203 80 const auto images = m_active->images;
204
1/1
✓ Branch 80 → 81 taken 80 times.
80 const auto defaultFont = m_active->defaultFont;
205 80 auto *cache = m_cache.get();
206 80 const auto cacheKey = m_active->cacheKey;
207
3/13
✓ Branch 88 → 89 taken 80 times.
✓ Branch 90 → 91 taken 80 times.
✓ Branch 91 → 92 taken 80 times.
✗ Branch 124 → 125 not taken.
✗ Branch 124 → 126 not taken.
✗ Branch 127 → 128 not taken.
✗ Branch 127 → 129 not taken.
✗ Branch 130 → 131 not taken.
✗ Branch 130 → 132 not taken.
✗ Branch 133 → 134 not taken.
✗ Branch 133 → 135 not taken.
✗ Branch 136 → 137 not taken.
✗ Branch 136 → 138 not taken.
80 watcher->setFuture(QtConcurrent::run(&m_pool, [input, decodedInput, cancelled, images, defaultFont, cache, cacheKey] {
208 6 const auto abandoned = [] {
209 6 NativeMailDocument result;
210 6 result.outcome = NativeMailDocument::Outcome::Cancelled;
211 6 return result;
212 };
213 80 const auto key = cacheKey;
214
2/2
✓ Branch 5 → 6 taken 5 times.
✓ Branch 5 → 7 taken 75 times.
80 if (cancelled->load()) return abandoned();
215
1/1
✓ Branch 7 → 8 taken 75 times.
75 const auto cached = cache->get(key);
216
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 75 times.
75 if (cancelled->load()) return abandoned();
217 75 QByteArray encoded = input;
218
6/6
✓ Branch 14 → 15 taken 68 times.
✓ Branch 14 → 18 taken 7 times.
✓ Branch 16 → 17 taken 15 times.
✓ Branch 16 → 18 taken 53 times.
✓ Branch 19 → 20 taken 15 times.
✓ Branch 19 → 32 taken 60 times.
75 if (!cached && !decodedInput.isNull()) {
219
1/1
✓ Branch 20 → 21 taken 15 times.
15 encoded = decodedInput.toUtf8();
220
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 28 taken 15 times.
15 if (encoded.size() > NativeMailDocument::MaxInputBytes) {
221 ✗ NativeMailDocument result;
222 ✗ result.outcome = NativeMailDocument::Outcome::Limited;
223 ✗ return result;
224 ✗ }
225
2/2
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 32 taken 14 times.
15 if (cancelled->load()) return abandoned();
226 }
227
3/3
✓ Branch 33 → 34 taken 7 times.
✓ Branch 33 → 36 taken 67 times.
✓ Branch 37 → 38 taken 67 times.
74 auto result = cached ? *cached : NativeMailDocument::prepare(encoded, cancelled.get());
228 74 result.cacheHit = cached.has_value();
229
5/6
✓ Branch 39 → 40 taken 73 times.
✓ Branch 39 → 44 taken 1 time.
✓ Branch 42 → 43 taken 73 times.
✗ Branch 42 → 44 not taken.
✓ Branch 45 → 46 taken 73 times.
✓ Branch 45 → 241 taken 1 time.
74 if (result.outcome == NativeMailDocument::Outcome::Ready && !cancelled->load()) {
230 class TextDocument : public QTextDocument {
231 ✗ QVariant loadResource(int, const QUrl &) override { return QByteArray(); }
232
1/1
✓ Branch 46 → 47 taken 73 times.
73 } document;
233
1/1
✓ Branch 47 → 48 taken 73 times.
73 document.setDefaultFont(defaultFont);
234
2/2
✓ Branch 49 → 50 taken 66 times.
✓ Branch 49 → 63 taken 7 times.
73 if (!cached) {
235
1/1
✓ Branch 50 → 51 taken 66 times.
66 document.setHtml(result.html);
236 // setHtml itself is not cooperative. Once it returns, skip its
237 // full-document export if another selection already owns the lane.
238
1/2
✗ Branch 53 → 54 not taken.
✓ Branch 53 → 56 taken 66 times.
66 if (cancelled->load()) return abandoned();
239
1/1
✓ Branch 56 → 57 taken 66 times.
66 result.plainText = document.toPlainText();
240
2/3
✓ Branch 61 → 62 taken 66 times.
✗ Branch 61 → 63 not taken.
✓ Branch 62 → 63 taken 66 times.
66 if (!cancelled->load()) cache->put(key, result);
241 }
242
1/2
✗ Branch 65 → 66 not taken.
✓ Branch 65 → 68 taken 73 times.
73 if (cancelled->load()) return abandoned();
243 73 QStringList ids;
244
4/4
✓ Branch 68 → 69 taken 73 times.
✓ Branch 69 → 70 taken 73 times.
✓ Branch 77 → 71 taken 12 times.
✓ Branch 77 → 78 taken 73 times.
85 for (const auto &reference : result.imageReferences)
245
3/3
✓ Branch 73 → 74 taken 5 times.
✓ Branch 73 → 75 taken 7 times.
✓ Branch 74 → 75 taken 5 times.
12 if (!reference.contentId.isEmpty()) ids.append(reference.contentId);
246
7/8
✓ Branch 79 → 80 taken 7 times.
✓ Branch 79 → 86 taken 66 times.
✓ Branch 81 → 82 taken 2 times.
✓ Branch 81 → 86 taken 5 times.
✓ Branch 82 → 83 taken 2 times.
✗ Branch 82 → 86 not taken.
✓ Branch 87 → 88 taken 2 times.
✓ Branch 87 → 101 taken 71 times.
75 if (cached && !ids.isEmpty() && images.loadInlineImages &&
247
1/2
✓ Branch 84 → 85 taken 2 times.
✗ Branch 84 → 86 not taken.
2 !decodedInput.isNull()) {
248
1/1
✓ Branch 88 → 89 taken 2 times.
2 encoded = decodedInput.toUtf8();
249
1/2
✗ Branch 92 → 93 not taken.
✓ Branch 92 → 96 taken 2 times.
2 if (encoded.size() > NativeMailDocument::MaxInputBytes) {
250 ✗ NativeMailDocument limited;
251 ✗ limited.outcome = NativeMailDocument::Outcome::Limited;
252 ✗ return limited;
253 ✗ }
254
1/2
✗ Branch 98 → 99 not taken.
✓ Branch 98 → 101 taken 2 times.
2 if (cancelled->load()) return abandoned();
255 }
256 73 auto imageContext = images;
257
2/2
✓ Branch 103 → 104 taken 16 times.
✓ Branch 103 → 105 taken 57 times.
73 if (!decodedInput.isNull()) imageContext.expectedHtml = encoded;
258 73 const auto decoded = images.loadInlineImages
259
3/3
✓ Branch 105 → 106 taken 63 times.
✓ Branch 105 → 108 taken 10 times.
✓ Branch 107 → 109 taken 63 times.
73 ? NativeImageResources::loadResident(imageContext, ids, cancelled.get()) : QHash<QString, QImage>{};
260
1/2
✗ Branch 111 → 112 not taken.
✓ Branch 111 → 114 taken 73 times.
73 if (cancelled->load()) return abandoned();
261
2/2
✓ Branch 154 → 115 taken 12 times.
✓ Branch 154 → 155 taken 73 times.
85 for (qsizetype i = 0; i < result.imageReferences.size(); ++i) {
262
1/2
✗ Branch 117 → 118 not taken.
✓ Branch 117 → 119 taken 12 times.
12 if (cancelled->load()) return abandoned();
263
1/1
✓ Branch 119 → 120 taken 12 times.
12 const auto &reference = result.imageReferences[i];
264 12 const auto found = decoded.constFind(reference.contentId);
265
2/2
✓ Branch 123 → 124 taken 9 times.
✓ Branch 123 → 125 taken 3 times.
12 if (found == decoded.cend()) continue;
266
2/2
✓ Branch 129 → 130 taken 3 times.
✓ Branch 130 → 131 taken 3 times.
6 const QUrl url(QStringLiteral("mailjd-native-image:%1").arg(i));
267
1/1
✓ Branch 135 → 136 taken 3 times.
3 result.images.insert(url, *found);
268
1/1
✓ Branch 143 → 144 taken 3 times.
3 result.html.replace(reference.placeholder,
269
2/2
✓ Branch 141 → 142 taken 3 times.
✓ Branch 142 → 143 taken 3 times.
9 QStringLiteral("<img src=\"%1\">").arg(url.toString()));
270 3 }
271
5/6
✓ Branch 155 → 156 taken 63 times.
✓ Branch 155 → 160 taken 10 times.
✓ Branch 158 → 159 taken 63 times.
✗ Branch 158 → 160 not taken.
✓ Branch 161 → 162 taken 63 times.
✓ Branch 161 → 223 taken 10 times.
73 if (images.loadInlineImages && !cancelled->load()) {
272 // Preserve the parsed rich-text structure across the worker handoff.
273 // The persistent cache stores only normalized strings and metadata.
274 // Resource-free fragments may also enter the bounded memory cache.
275
7/7
✓ Branch 163 → 164 taken 57 times.
✓ Branch 163 → 166 taken 6 times.
✓ Branch 165 → 166 taken 3 times.
✓ Branch 165 → 167 taken 54 times.
✓ Branch 168 → 169 taken 9 times.
✓ Branch 168 → 170 taken 54 times.
✓ Branch 169 → 170 taken 9 times.
63 if (cached || !result.images.isEmpty()) document.setHtml(result.html);
276
1/2
✗ Branch 172 → 173 not taken.
✓ Branch 172 → 174 taken 63 times.
63 if (cancelled->load()) return abandoned();
277 // Apply the same table spacing before capturing the reusable rich
278 // fragment. Walking and rewriting every table on the GUI callback
279 // delays the first visible HTML-light frame.
280
2/2
✓ Branch 174 → 175 taken 63 times.
✓ Branch 175 → 176 taken 63 times.
63 auto frames = document.rootFrame()->childFrames();
281 126 const auto padding = qMax(4.0,
282
3/3
✓ Branch 176 → 177 taken 63 times.
✓ Branch 177 → 178 taken 63 times.
✓ Branch 178 → 179 taken 63 times.
63 QFontMetricsF(document.defaultFont()).height() / 3.0);
283
2/2
✓ Branch 200 → 183 taken 11 times.
✓ Branch 200 → 201 taken 63 times.
74 while (!frames.isEmpty()) {
284
1/2
✗ Branch 185 → 186 not taken.
✓ Branch 185 → 188 taken 11 times.
11 if (cancelled->load()) return abandoned();
285
1/1
✓ Branch 188 → 189 taken 11 times.
11 auto *frame = frames.takeLast();
286
2/2
✓ Branch 189 → 190 taken 11 times.
✓ Branch 190 → 191 taken 11 times.
11 frames.append(frame->childFrames());
287
3/3
✓ Branch 192 → 193 taken 11 times.
✓ Branch 193 → 194 taken 8 times.
✓ Branch 193 → 199 taken 3 times.
11 if (auto *table = qobject_cast<QTextTable *>(frame)) {
288
1/1
✓ Branch 194 → 195 taken 8 times.
8 auto format = table->format();
289
1/1
✓ Branch 195 → 196 taken 8 times.
8 format.setCellPadding(padding);
290
1/1
✓ Branch 196 → 197 taken 8 times.
8 table->setFormat(format);
291 8 }
292 }
293
1/2
✗ Branch 203 → 204 not taken.
✓ Branch 203 → 206 taken 63 times.
63 if (cancelled->load()) return abandoned();
294
1/1
✓ Branch 206 → 207 taken 63 times.
63 result.fragment = QSharedPointer<const QTextDocumentFragment>::create(&document);
295
1/2
✗ Branch 211 → 212 not taken.
✓ Branch 211 → 214 taken 63 times.
63 if (cancelled->load()) return abandoned();
296 // Qt 6.8 lazily creates the fragment's private root frame when a
297 // cursor first traverses a simple document. Materialize it on the
298 // worker before read-only insertion on the GUI thread. The export
299 // is discarded here; it never replaces normalized or source bytes.
300
1/1
✓ Branch 215 → 216 taken 63 times.
63 (void)result.fragment->toHtml();
301
302 63 }
303 73 }
304 74 return result;
305 80 }));
306 80 }
307