1/*
2 * Copyright (C) 2019 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "ResourceLoadStatisticsStore.h"
28
29#if ENABLE(RESOURCE_LOAD_STATISTICS)
30
31#include "Logging.h"
32#include "NetworkSession.h"
33#include "PluginProcessManager.h"
34#include "PluginProcessProxy.h"
35#include "ResourceLoadStatisticsPersistentStorage.h"
36#include "StorageAccessStatus.h"
37#include "WebProcessProxy.h"
38#include "WebResourceLoadStatisticsTelemetry.h"
39#include "WebsiteDataStore.h"
40#include <WebCore/CookieJar.h>
41#include <WebCore/KeyedCoding.h>
42#include <WebCore/NetworkStorageSession.h>
43#include <WebCore/ResourceLoadStatistics.h>
44#include <wtf/CallbackAggregator.h>
45#include <wtf/DateMath.h>
46#include <wtf/MathExtras.h>
47#include <wtf/text/StringBuilder.h>
48
49namespace WebKit {
50using namespace WebCore;
51
52constexpr Seconds minimumStatisticsProcessingInterval { 5_s };
53constexpr unsigned operatingDatesWindowLong { 30 };
54constexpr unsigned operatingDatesWindowShort { 7 };
55
56#if !RELEASE_LOG_DISABLED
57static String domainsToString(const Vector<RegistrableDomain>& domains)
58{
59 StringBuilder builder;
60 for (auto& domain : domains) {
61 if (!builder.isEmpty())
62 builder.appendLiteral(", ");
63 builder.append(domain.string());
64 }
65 return builder.toString();
66}
67
68static String domainsToString(const HashMap<RegistrableDomain, WebsiteDataToRemove>& domainsToRemoveWebsiteDataFor)
69{
70 StringBuilder builder;
71 for (auto& domain : domainsToRemoveWebsiteDataFor.keys()) {
72 if (!builder.isEmpty())
73 builder.appendLiteral(", ");
74 builder.append(domain.string());
75 switch (domainsToRemoveWebsiteDataFor.get(domain)) {
76 case WebsiteDataToRemove::All:
77 builder.appendLiteral("(all data)");
78 break;
79 case WebsiteDataToRemove::AllButHttpOnlyCookies:
80 builder.appendLiteral("(all but HttpOnly cookies)");
81 break;
82 case WebsiteDataToRemove::AllButCookies:
83 builder.appendLiteral("(all but cookies)");
84 break;
85 }
86 }
87 return builder.toString();
88}
89#endif
90
91OperatingDate OperatingDate::fromWallTime(WallTime time)
92{
93 double ms = time.secondsSinceEpoch().milliseconds();
94 int year = msToYear(ms);
95 int yearDay = dayInYear(ms, year);
96 int month = monthFromDayInYear(yearDay, isLeapYear(year));
97 int monthDay = dayInMonthFromDayInYear(yearDay, isLeapYear(year));
98
99 return OperatingDate { year, month, monthDay };
100}
101
102OperatingDate OperatingDate::today()
103{
104 return OperatingDate::fromWallTime(WallTime::now());
105}
106
107Seconds OperatingDate::secondsSinceEpoch() const
108{
109 return Seconds { dateToDaysFrom1970(m_year, m_month, m_monthDay) * secondsPerDay };
110}
111
112bool OperatingDate::operator==(const OperatingDate& other) const
113{
114 return m_monthDay == other.m_monthDay && m_month == other.m_month && m_year == other.m_year;
115}
116
117bool OperatingDate::operator<(const OperatingDate& other) const
118{
119 return secondsSinceEpoch() < other.secondsSinceEpoch();
120}
121
122bool OperatingDate::operator<=(const OperatingDate& other) const
123{
124 return secondsSinceEpoch() <= other.secondsSinceEpoch();
125}
126
127ResourceLoadStatisticsStore::ResourceLoadStatisticsStore(WebResourceLoadStatisticsStore& store, WorkQueue& workQueue, ShouldIncludeLocalhost shouldIncludeLocalhost)
128 : m_store(store)
129 , m_workQueue(workQueue)
130 , m_shouldIncludeLocalhost(shouldIncludeLocalhost)
131{
132 ASSERT(!RunLoop::isMain());
133
134 includeTodayAsOperatingDateIfNecessary();
135}
136
137ResourceLoadStatisticsStore::~ResourceLoadStatisticsStore()
138{
139 ASSERT(!RunLoop::isMain());
140}
141
142unsigned ResourceLoadStatisticsStore::computeImportance(const ResourceLoadStatistics& resourceStatistic)
143{
144 unsigned importance = ResourceLoadStatisticsStore::maxImportance;
145 if (!resourceStatistic.isPrevalentResource)
146 importance -= 1;
147 if (!resourceStatistic.hadUserInteraction)
148 importance -= 2;
149 return importance;
150}
151
152void ResourceLoadStatisticsStore::setNotifyPagesWhenDataRecordsWereScanned(bool value)
153{
154 ASSERT(!RunLoop::isMain());
155 m_parameters.shouldNotifyPagesWhenDataRecordsWereScanned = value;
156}
157
158bool ResourceLoadStatisticsStore::shouldSkip(const RegistrableDomain& domain) const
159{
160 ASSERT(!RunLoop::isMain());
161 return !(parameters().isRunningTest)
162 && m_shouldIncludeLocalhost == ShouldIncludeLocalhost::No && domain.string() == "localhost";
163}
164
165void ResourceLoadStatisticsStore::setIsRunningTest(bool value)
166{
167 ASSERT(!RunLoop::isMain());
168 m_parameters.isRunningTest = value;
169}
170
171void ResourceLoadStatisticsStore::setShouldClassifyResourcesBeforeDataRecordsRemoval(bool value)
172{
173 ASSERT(!RunLoop::isMain());
174 m_parameters.shouldClassifyResourcesBeforeDataRecordsRemoval = value;
175}
176
177void ResourceLoadStatisticsStore::setShouldSubmitTelemetry(bool value)
178{
179 ASSERT(!RunLoop::isMain());
180 m_parameters.shouldSubmitTelemetry = value;
181}
182
183void ResourceLoadStatisticsStore::removeDataRecords(CompletionHandler<void()>&& completionHandler)
184{
185 ASSERT(!RunLoop::isMain());
186
187 if (!shouldRemoveDataRecords()) {
188 completionHandler();
189 return;
190 }
191
192#if ENABLE(NETSCAPE_PLUGIN_API)
193 m_activePluginTokens.clear();
194 for (const auto& plugin : PluginProcessManager::singleton().pluginProcesses())
195 m_activePluginTokens.add(plugin->pluginProcessToken());
196#endif
197
198 auto domainsToRemoveWebsiteDataFor = registrableDomainsToRemoveWebsiteDataFor();
199 if (domainsToRemoveWebsiteDataFor.isEmpty()) {
200 completionHandler();
201 return;
202 }
203
204#if !RELEASE_LOG_DISABLED
205 RELEASE_LOG_INFO_IF(m_debugLoggingEnabled, ResourceLoadStatisticsDebug, "About to remove data records for %{public}s.", domainsToString(domainsToRemoveWebsiteDataFor).utf8().data());
206#endif
207
208 setDataRecordsBeingRemoved(true);
209
210 RunLoop::main().dispatch([domainsToRemoveWebsiteDataFor = crossThreadCopy(domainsToRemoveWebsiteDataFor), completionHandler = WTFMove(completionHandler), weakThis = makeWeakPtr(*this), shouldNotifyPagesWhenDataRecordsWereScanned = m_parameters.shouldNotifyPagesWhenDataRecordsWereScanned, workQueue = m_workQueue.copyRef()] () mutable {
211 if (!weakThis) {
212 completionHandler();
213 return;
214 }
215
216 weakThis->m_store.deleteWebsiteDataForRegistrableDomains(WebResourceLoadStatisticsStore::monitoredDataTypes(), WTFMove(domainsToRemoveWebsiteDataFor), shouldNotifyPagesWhenDataRecordsWereScanned, [completionHandler = WTFMove(completionHandler), weakThis = WTFMove(weakThis), workQueue = workQueue.copyRef()](const HashSet<RegistrableDomain>& domainsWithDeletedWebsiteData) mutable {
217 workQueue->dispatch([domainsWithDeletedWebsiteData = crossThreadCopy(domainsWithDeletedWebsiteData), completionHandler = WTFMove(completionHandler), weakThis = WTFMove(weakThis)] () mutable {
218 if (!weakThis) {
219 completionHandler();
220 return;
221 }
222 weakThis->incrementRecordsDeletedCountForDomains(WTFMove(domainsWithDeletedWebsiteData));
223 weakThis->setDataRecordsBeingRemoved(false);
224 weakThis->m_store.tryDumpResourceLoadStatistics();
225 completionHandler();
226#if !RELEASE_LOG_DISABLED
227 RELEASE_LOG_INFO_IF(weakThis->m_debugLoggingEnabled, ResourceLoadStatisticsDebug, "Done removing data records.");
228#endif
229 });
230 });
231 });
232}
233
234void ResourceLoadStatisticsStore::processStatisticsAndDataRecords()
235{
236 ASSERT(!RunLoop::isMain());
237
238 if (m_parameters.shouldClassifyResourcesBeforeDataRecordsRemoval)
239 classifyPrevalentResources();
240
241 removeDataRecords([this, weakThis = makeWeakPtr(*this)] () mutable {
242 ASSERT(!RunLoop::isMain());
243 if (!weakThis)
244 return;
245
246 pruneStatisticsIfNeeded();
247 syncStorageIfNeeded();
248
249 if (!m_parameters.shouldNotifyPagesWhenDataRecordsWereScanned)
250 return;
251
252 RunLoop::main().dispatch([this, weakThis = WTFMove(weakThis)] {
253 ASSERT(RunLoop::isMain());
254 if (!weakThis)
255 return;
256
257 m_store.notifyResourceLoadStatisticsProcessed();
258 });
259 });
260}
261
262void ResourceLoadStatisticsStore::grandfatherExistingWebsiteData(CompletionHandler<void()>&& callback)
263{
264 ASSERT(!RunLoop::isMain());
265
266 RunLoop::main().dispatch([weakThis = makeWeakPtr(*this), callback = WTFMove(callback), shouldNotifyPagesWhenDataRecordsWereScanned = m_parameters.shouldNotifyPagesWhenDataRecordsWereScanned, workQueue = m_workQueue.copyRef(), store = makeRef(m_store)] () mutable {
267 store->registrableDomainsWithWebsiteData(WebResourceLoadStatisticsStore::monitoredDataTypes(), shouldNotifyPagesWhenDataRecordsWereScanned, [weakThis = WTFMove(weakThis), callback = WTFMove(callback), workQueue = workQueue.copyRef()] (HashSet<RegistrableDomain>&& domainsWithWebsiteData) mutable {
268 workQueue->dispatch([weakThis = WTFMove(weakThis), domainsWithWebsiteData = crossThreadCopy(domainsWithWebsiteData), callback = WTFMove(callback)] () mutable {
269 if (!weakThis) {
270 callback();
271 return;
272 }
273
274 weakThis->grandfatherDataForDomains(domainsWithWebsiteData);
275 weakThis->m_endOfGrandfatheringTimestamp = WallTime::now() + weakThis->m_parameters.grandfatheringTime;
276 weakThis->syncStorageImmediately();
277 callback();
278 weakThis->logTestingEvent("Grandfathered"_s);
279 });
280 });
281 });
282}
283
284void ResourceLoadStatisticsStore::setResourceLoadStatisticsDebugMode(bool enable)
285{
286 ASSERT(!RunLoop::isMain());
287
288#if !RELEASE_LOG_DISABLED
289 if (enable)
290 RELEASE_LOG_INFO(ResourceLoadStatisticsDebug, "Turned ITP Debug Mode on.");
291#endif
292
293 m_debugModeEnabled = enable;
294 m_debugLoggingEnabled = enable;
295
296 ensurePrevalentResourcesForDebugMode();
297 // This will log the current cookie blocking state.
298 if (enable)
299 updateCookieBlocking([]() { });
300}
301
302void ResourceLoadStatisticsStore::setPrevalentResourceForDebugMode(const RegistrableDomain& domain)
303{
304 m_debugManualPrevalentResource = domain;
305}
306
307void ResourceLoadStatisticsStore::scheduleStatisticsProcessingRequestIfNecessary()
308{
309 ASSERT(!RunLoop::isMain());
310
311 m_pendingStatisticsProcessingRequestIdentifier = ++m_lastStatisticsProcessingRequestIdentifier;
312 m_workQueue->dispatchAfter(minimumStatisticsProcessingInterval, [this, weakThis = makeWeakPtr(*this), statisticsProcessingRequestIdentifier = *m_pendingStatisticsProcessingRequestIdentifier] {
313 if (!weakThis)
314 return;
315
316 if (!m_pendingStatisticsProcessingRequestIdentifier || *m_pendingStatisticsProcessingRequestIdentifier != statisticsProcessingRequestIdentifier) {
317 // This request has been canceled.
318 return;
319 }
320
321 updateCookieBlocking([]() { });
322 processStatisticsAndDataRecords();
323 });
324}
325
326void ResourceLoadStatisticsStore::cancelPendingStatisticsProcessingRequest()
327{
328 ASSERT(!RunLoop::isMain());
329
330 m_pendingStatisticsProcessingRequestIdentifier = WTF::nullopt;
331}
332
333void ResourceLoadStatisticsStore::setTimeToLiveUserInteraction(Seconds seconds)
334{
335 ASSERT(!RunLoop::isMain());
336 ASSERT(seconds >= 0_s);
337
338 m_parameters.timeToLiveUserInteraction = seconds;
339}
340
341void ResourceLoadStatisticsStore::setMinimumTimeBetweenDataRecordsRemoval(Seconds seconds)
342{
343 ASSERT(!RunLoop::isMain());
344 ASSERT(seconds >= 0_s);
345
346 m_parameters.minimumTimeBetweenDataRecordsRemoval = seconds;
347}
348
349void ResourceLoadStatisticsStore::setGrandfatheringTime(Seconds seconds)
350{
351 ASSERT(!RunLoop::isMain());
352 ASSERT(seconds >= 0_s);
353
354 m_parameters.grandfatheringTime = seconds;
355}
356
357void ResourceLoadStatisticsStore::setCacheMaxAgeCap(Seconds seconds)
358{
359 ASSERT(!RunLoop::isMain());
360 ASSERT(seconds >= 0_s);
361
362 m_parameters.cacheMaxAgeCapTime = seconds;
363 updateCacheMaxAgeCap();
364}
365
366void ResourceLoadStatisticsStore::updateCacheMaxAgeCap()
367{
368 ASSERT(!RunLoop::isMain());
369
370 RunLoop::main().dispatch([store = makeRef(m_store), seconds = m_parameters.cacheMaxAgeCapTime] () {
371 store->setCacheMaxAgeCap(seconds, [] { });
372 });
373}
374
375void ResourceLoadStatisticsStore::setAgeCapForClientSideCookies(Seconds seconds)
376{
377 ASSERT(!RunLoop::isMain());
378 ASSERT(seconds >= 0_s);
379
380 m_parameters.clientSideCookiesAgeCapTime = seconds;
381 updateClientSideCookiesAgeCap();
382}
383
384void ResourceLoadStatisticsStore::updateClientSideCookiesAgeCap()
385{
386 ASSERT(!RunLoop::isMain());
387
388#if ENABLE(RESOURCE_LOAD_STATISTICS)
389 RunLoop::main().dispatch([store = makeRef(m_store), seconds = m_parameters.clientSideCookiesAgeCapTime] () {
390 if (auto* networkSession = store->networkSession())
391 networkSession->networkStorageSession().setAgeCapForClientSideCookies(seconds);
392 });
393#endif
394}
395
396bool ResourceLoadStatisticsStore::shouldRemoveDataRecords() const
397{
398 ASSERT(!RunLoop::isMain());
399
400 if (m_dataRecordsBeingRemoved)
401 return false;
402
403#if ENABLE(NETSCAPE_PLUGIN_API)
404 for (const auto& plugin : PluginProcessManager::singleton().pluginProcesses()) {
405 if (!m_activePluginTokens.contains(plugin->pluginProcessToken()))
406 return true;
407 }
408#endif
409
410 return !m_lastTimeDataRecordsWereRemoved || MonotonicTime::now() >= (m_lastTimeDataRecordsWereRemoved + m_parameters.minimumTimeBetweenDataRecordsRemoval) || parameters().isRunningTest;
411}
412
413void ResourceLoadStatisticsStore::setDataRecordsBeingRemoved(bool value)
414{
415 ASSERT(!RunLoop::isMain());
416
417 m_dataRecordsBeingRemoved = value;
418 if (m_dataRecordsBeingRemoved)
419 m_lastTimeDataRecordsWereRemoved = MonotonicTime::now();
420}
421
422void ResourceLoadStatisticsStore::updateCookieBlockingForDomains(const Vector<RegistrableDomain>& domainsToBlock, CompletionHandler<void()>&& completionHandler)
423{
424 ASSERT(!RunLoop::isMain());
425
426 RunLoop::main().dispatch([store = makeRef(m_store), domainsToBlock = crossThreadCopy(domainsToBlock), completionHandler = WTFMove(completionHandler)] () mutable {
427 store->callUpdatePrevalentDomainsToBlockCookiesForHandler(domainsToBlock, [store = store.copyRef(), completionHandler = WTFMove(completionHandler)]() mutable {
428 store->statisticsQueue().dispatch([completionHandler = WTFMove(completionHandler)]() mutable {
429 completionHandler();
430 });
431 });
432 });
433}
434
435
436void ResourceLoadStatisticsStore::clearBlockingStateForDomains(const Vector<RegistrableDomain>& domains, CompletionHandler<void()>&& completionHandler)
437{
438 ASSERT(!RunLoop::isMain());
439
440 if (domains.isEmpty()) {
441 completionHandler();
442 return;
443 }
444
445 RunLoop::main().dispatch([store = makeRef(m_store), domains = crossThreadCopy(domains)] {
446 store->callRemoveDomainsHandler(domains);
447 });
448
449 completionHandler();
450}
451
452Optional<Seconds> ResourceLoadStatisticsStore::statisticsEpirationTime() const
453{
454 if (m_parameters.timeToLiveUserInteraction)
455 return WallTime::now().secondsSinceEpoch() - m_parameters.timeToLiveUserInteraction.value();
456
457 if (m_operatingDates.size() >= operatingDatesWindowLong)
458 return m_operatingDates.first().secondsSinceEpoch();
459
460 return WTF::nullopt;
461}
462
463Vector<OperatingDate> ResourceLoadStatisticsStore::mergeOperatingDates(const Vector<OperatingDate>& existingDates, Vector<OperatingDate>&& newDates)
464{
465 if (existingDates.isEmpty())
466 return WTFMove(newDates);
467
468 Vector<OperatingDate> mergedDates(existingDates.size() + newDates.size());
469
470 // Merge the two sorted vectors of dates.
471 std::merge(existingDates.begin(), existingDates.end(), newDates.begin(), newDates.end(), mergedDates.begin());
472 // Remove duplicate dates.
473 removeRepeatedElements(mergedDates);
474
475 // Drop old dates until the Vector size reaches operatingDatesWindowLong.
476 while (mergedDates.size() > operatingDatesWindowLong)
477 mergedDates.remove(0);
478
479 return mergedDates;
480}
481
482void ResourceLoadStatisticsStore::mergeOperatingDates(Vector<OperatingDate>&& newDates)
483{
484 m_operatingDates = mergeOperatingDates(m_operatingDates, WTFMove(newDates));
485}
486
487void ResourceLoadStatisticsStore::includeTodayAsOperatingDateIfNecessary()
488{
489 ASSERT(!RunLoop::isMain());
490
491 auto today = OperatingDate::today();
492 if (!m_operatingDates.isEmpty() && today <= m_operatingDates.last())
493 return;
494
495 while (m_operatingDates.size() >= operatingDatesWindowLong)
496 m_operatingDates.remove(0);
497
498 m_operatingDates.append(today);
499}
500
501bool ResourceLoadStatisticsStore::hasStatisticsExpired(WallTime mostRecentUserInteractionTime, OperatingDatesWindow operatingDatesWindow) const
502{
503 ASSERT(!RunLoop::isMain());
504
505 unsigned operatingDatesWindowInDays = (operatingDatesWindow == OperatingDatesWindow::Long ? operatingDatesWindowLong : operatingDatesWindowShort);
506 if (m_operatingDates.size() >= operatingDatesWindowInDays) {
507 if (OperatingDate::fromWallTime(mostRecentUserInteractionTime) < m_operatingDates.first())
508 return true;
509 }
510
511 // If we don't meet the real criteria for an expired statistic, check the user setting for a tighter restriction (mainly for testing).
512 if (m_parameters.timeToLiveUserInteraction) {
513 if (WallTime::now() > mostRecentUserInteractionTime + m_parameters.timeToLiveUserInteraction.value())
514 return true;
515 }
516
517 return false;
518}
519
520bool ResourceLoadStatisticsStore::hasStatisticsExpired(const ResourceLoadStatistics& resourceStatistic, OperatingDatesWindow operatingDatesWindow) const
521{
522 return hasStatisticsExpired(resourceStatistic.mostRecentUserInteractionTime, operatingDatesWindow);
523}
524
525void ResourceLoadStatisticsStore::setMaxStatisticsEntries(size_t maximumEntryCount)
526{
527 ASSERT(!RunLoop::isMain());
528
529 m_parameters.maxStatisticsEntries = maximumEntryCount;
530}
531
532void ResourceLoadStatisticsStore::setPruneEntriesDownTo(size_t pruneTargetCount)
533{
534 ASSERT(!RunLoop::isMain());
535
536 m_parameters.pruneEntriesDownTo = pruneTargetCount;
537}
538
539void ResourceLoadStatisticsStore::resetParametersToDefaultValues()
540{
541 ASSERT(!RunLoop::isMain());
542
543 m_parameters = { };
544}
545
546void ResourceLoadStatisticsStore::logTestingEvent(const String& event)
547{
548 ASSERT(!RunLoop::isMain());
549
550 RunLoop::main().dispatch([store = makeRef(m_store), event = event.isolatedCopy()] {
551 store->logTestingEvent(event);
552 });
553}
554
555void ResourceLoadStatisticsStore::removeAllStorageAccess(CompletionHandler<void()>&& completionHandler)
556{
557 ASSERT(!RunLoop::isMain());
558 RunLoop::main().dispatch([store = makeRef(m_store), completionHandler = WTFMove(completionHandler)]() mutable {
559 store->removeAllStorageAccess([store = store.copyRef(), completionHandler = WTFMove(completionHandler)]() mutable {
560 store->statisticsQueue().dispatch([completionHandler = WTFMove(completionHandler)]() mutable {
561 completionHandler();
562 });
563 });
564 });
565}
566
567void ResourceLoadStatisticsStore::didCreateNetworkProcess()
568{
569 ASSERT(!RunLoop::isMain());
570
571 updateCookieBlocking([]() { });
572 updateCacheMaxAgeCap();
573 updateClientSideCookiesAgeCap();
574}
575
576void ResourceLoadStatisticsStore::debugLogDomainsInBatches(const char* action, const Vector<RegistrableDomain>& domains)
577{
578#if !RELEASE_LOG_DISABLED
579 static const auto maxNumberOfDomainsInOneLogStatement = 50;
580 if (domains.isEmpty())
581 return;
582
583 if (domains.size() <= maxNumberOfDomainsInOneLogStatement) {
584 RELEASE_LOG_INFO(ResourceLoadStatisticsDebug, "About to %{public}s cookies in third-party contexts for: %{public}s.", action, domainsToString(domains).utf8().data());
585 return;
586 }
587
588 Vector<RegistrableDomain> batch;
589 batch.reserveInitialCapacity(maxNumberOfDomainsInOneLogStatement);
590 auto batchNumber = 1;
591 unsigned numberOfBatches = std::ceil(domains.size() / static_cast<float>(maxNumberOfDomainsInOneLogStatement));
592
593 for (auto& domain : domains) {
594 if (batch.size() == maxNumberOfDomainsInOneLogStatement) {
595 RELEASE_LOG_INFO(ResourceLoadStatisticsDebug, "About to %{public}s cookies in third-party contexts for (%{public}d of %u): %{public}s.", action, batchNumber, numberOfBatches, domainsToString(batch).utf8().data());
596 batch.shrink(0);
597 ++batchNumber;
598 }
599 batch.append(domain);
600 }
601 if (!batch.isEmpty())
602 RELEASE_LOG_INFO(ResourceLoadStatisticsDebug, "About to %{public}s cookies in third-party contexts for (%{public}d of %u): %{public}s.", action, batchNumber, numberOfBatches, domainsToString(batch).utf8().data());
603#else
604 UNUSED_PARAM(action);
605 UNUSED_PARAM(domains);
606#endif
607}
608
609} // namespace WebKit
610
611#endif
612