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 completionHandler();
225#if !RELEASE_LOG_DISABLED
226 RELEASE_LOG_INFO_IF(weakThis->m_debugLoggingEnabled, ResourceLoadStatisticsDebug, "Done removing data records.");
227#endif
228 });
229 });
230 });
231}
232
233void ResourceLoadStatisticsStore::processStatisticsAndDataRecords()
234{
235 ASSERT(!RunLoop::isMain());
236
237 if (m_parameters.shouldClassifyResourcesBeforeDataRecordsRemoval)
238 classifyPrevalentResources();
239
240 removeDataRecords([this, weakThis = makeWeakPtr(*this)] () mutable {
241 ASSERT(!RunLoop::isMain());
242 if (!weakThis)
243 return;
244
245 pruneStatisticsIfNeeded();
246 syncStorageIfNeeded();
247
248 if (!m_parameters.shouldNotifyPagesWhenDataRecordsWereScanned)
249 return;
250
251 RunLoop::main().dispatch([this, weakThis = WTFMove(weakThis)] {
252 ASSERT(RunLoop::isMain());
253 if (!weakThis)
254 return;
255
256 m_store.notifyResourceLoadStatisticsProcessed();
257 });
258 });
259}
260
261void ResourceLoadStatisticsStore::grandfatherExistingWebsiteData(CompletionHandler<void()>&& callback)
262{
263 ASSERT(!RunLoop::isMain());
264
265 RunLoop::main().dispatch([weakThis = makeWeakPtr(*this), callback = WTFMove(callback), shouldNotifyPagesWhenDataRecordsWereScanned = m_parameters.shouldNotifyPagesWhenDataRecordsWereScanned, workQueue = m_workQueue.copyRef(), store = makeRef(m_store)] () mutable {
266 store->registrableDomainsWithWebsiteData(WebResourceLoadStatisticsStore::monitoredDataTypes(), shouldNotifyPagesWhenDataRecordsWereScanned, [weakThis = WTFMove(weakThis), callback = WTFMove(callback), workQueue = workQueue.copyRef()] (HashSet<RegistrableDomain>&& domainsWithWebsiteData) mutable {
267 workQueue->dispatch([weakThis = WTFMove(weakThis), domainsWithWebsiteData = crossThreadCopy(domainsWithWebsiteData), callback = WTFMove(callback)] () mutable {
268 if (!weakThis) {
269 callback();
270 return;
271 }
272
273 weakThis->grandfatherDataForDomains(domainsWithWebsiteData);
274 weakThis->m_endOfGrandfatheringTimestamp = WallTime::now() + weakThis->m_parameters.grandfatheringTime;
275 weakThis->syncStorageImmediately();
276 callback();
277 weakThis->logTestingEvent("Grandfathered"_s);
278 });
279 });
280 });
281}
282
283void ResourceLoadStatisticsStore::setResourceLoadStatisticsDebugMode(bool enable)
284{
285 ASSERT(!RunLoop::isMain());
286
287#if !RELEASE_LOG_DISABLED
288 if (enable)
289 RELEASE_LOG_INFO(ResourceLoadStatisticsDebug, "Turned ITP Debug Mode on.");
290#endif
291
292 m_debugModeEnabled = enable;
293 m_debugLoggingEnabled = enable;
294
295 ensurePrevalentResourcesForDebugMode();
296 // This will log the current cookie blocking state.
297 if (enable)
298 updateCookieBlocking([]() { });
299}
300
301void ResourceLoadStatisticsStore::setPrevalentResourceForDebugMode(const RegistrableDomain& domain)
302{
303 m_debugManualPrevalentResource = domain;
304}
305
306void ResourceLoadStatisticsStore::scheduleStatisticsProcessingRequestIfNecessary()
307{
308 ASSERT(!RunLoop::isMain());
309
310 m_pendingStatisticsProcessingRequestIdentifier = ++m_lastStatisticsProcessingRequestIdentifier;
311 m_workQueue->dispatchAfter(minimumStatisticsProcessingInterval, [this, weakThis = makeWeakPtr(*this), statisticsProcessingRequestIdentifier = *m_pendingStatisticsProcessingRequestIdentifier] {
312 if (!weakThis)
313 return;
314
315 if (!m_pendingStatisticsProcessingRequestIdentifier || *m_pendingStatisticsProcessingRequestIdentifier != statisticsProcessingRequestIdentifier) {
316 // This request has been canceled.
317 return;
318 }
319
320 updateCookieBlocking([]() { });
321 processStatisticsAndDataRecords();
322 });
323}
324
325void ResourceLoadStatisticsStore::cancelPendingStatisticsProcessingRequest()
326{
327 ASSERT(!RunLoop::isMain());
328
329 m_pendingStatisticsProcessingRequestIdentifier = WTF::nullopt;
330}
331
332void ResourceLoadStatisticsStore::setTimeToLiveUserInteraction(Seconds seconds)
333{
334 ASSERT(!RunLoop::isMain());
335 ASSERT(seconds >= 0_s);
336
337 m_parameters.timeToLiveUserInteraction = seconds;
338}
339
340void ResourceLoadStatisticsStore::setMinimumTimeBetweenDataRecordsRemoval(Seconds seconds)
341{
342 ASSERT(!RunLoop::isMain());
343 ASSERT(seconds >= 0_s);
344
345 m_parameters.minimumTimeBetweenDataRecordsRemoval = seconds;
346}
347
348void ResourceLoadStatisticsStore::setGrandfatheringTime(Seconds seconds)
349{
350 ASSERT(!RunLoop::isMain());
351 ASSERT(seconds >= 0_s);
352
353 m_parameters.grandfatheringTime = seconds;
354}
355
356void ResourceLoadStatisticsStore::setCacheMaxAgeCap(Seconds seconds)
357{
358 ASSERT(!RunLoop::isMain());
359 ASSERT(seconds >= 0_s);
360
361 m_parameters.cacheMaxAgeCapTime = seconds;
362 updateCacheMaxAgeCap();
363}
364
365void ResourceLoadStatisticsStore::updateCacheMaxAgeCap()
366{
367 ASSERT(!RunLoop::isMain());
368
369 RunLoop::main().dispatch([store = makeRef(m_store), seconds = m_parameters.cacheMaxAgeCapTime] () {
370 store->setCacheMaxAgeCap(seconds, [] { });
371 });
372}
373
374void ResourceLoadStatisticsStore::setAgeCapForClientSideCookies(Seconds seconds)
375{
376 ASSERT(!RunLoop::isMain());
377 ASSERT(seconds >= 0_s);
378
379 m_parameters.clientSideCookiesAgeCapTime = seconds;
380 updateClientSideCookiesAgeCap();
381}
382
383void ResourceLoadStatisticsStore::updateClientSideCookiesAgeCap()
384{
385 ASSERT(!RunLoop::isMain());
386
387#if ENABLE(RESOURCE_LOAD_STATISTICS)
388 RunLoop::main().dispatch([store = makeRef(m_store), seconds = m_parameters.clientSideCookiesAgeCapTime] () {
389 if (auto* networkSession = store->networkSession())
390 networkSession->networkStorageSession().setAgeCapForClientSideCookies(seconds);
391 });
392#endif
393}
394
395bool ResourceLoadStatisticsStore::shouldRemoveDataRecords() const
396{
397 ASSERT(!RunLoop::isMain());
398
399 if (m_dataRecordsBeingRemoved)
400 return false;
401
402#if ENABLE(NETSCAPE_PLUGIN_API)
403 for (const auto& plugin : PluginProcessManager::singleton().pluginProcesses()) {
404 if (!m_activePluginTokens.contains(plugin->pluginProcessToken()))
405 return true;
406 }
407#endif
408
409 return !m_lastTimeDataRecordsWereRemoved || MonotonicTime::now() >= (m_lastTimeDataRecordsWereRemoved + m_parameters.minimumTimeBetweenDataRecordsRemoval) || parameters().isRunningTest;
410}
411
412void ResourceLoadStatisticsStore::setDataRecordsBeingRemoved(bool value)
413{
414 ASSERT(!RunLoop::isMain());
415
416 m_dataRecordsBeingRemoved = value;
417 if (m_dataRecordsBeingRemoved)
418 m_lastTimeDataRecordsWereRemoved = MonotonicTime::now();
419}
420
421void ResourceLoadStatisticsStore::updateCookieBlockingForDomains(const Vector<RegistrableDomain>& domainsToBlock, CompletionHandler<void()>&& completionHandler)
422{
423 ASSERT(!RunLoop::isMain());
424
425 RunLoop::main().dispatch([store = makeRef(m_store), domainsToBlock = crossThreadCopy(domainsToBlock), completionHandler = WTFMove(completionHandler)] () mutable {
426 store->callUpdatePrevalentDomainsToBlockCookiesForHandler(domainsToBlock, [store = store.copyRef(), completionHandler = WTFMove(completionHandler)]() mutable {
427 store->statisticsQueue().dispatch([completionHandler = WTFMove(completionHandler)]() mutable {
428 completionHandler();
429 });
430 });
431 });
432}
433
434
435void ResourceLoadStatisticsStore::clearBlockingStateForDomains(const Vector<RegistrableDomain>& domains, CompletionHandler<void()>&& completionHandler)
436{
437 ASSERT(!RunLoop::isMain());
438
439 if (domains.isEmpty()) {
440 completionHandler();
441 return;
442 }
443
444 RunLoop::main().dispatch([store = makeRef(m_store), domains = crossThreadCopy(domains)] {
445 store->callRemoveDomainsHandler(domains);
446 });
447
448 completionHandler();
449}
450
451Optional<Seconds> ResourceLoadStatisticsStore::statisticsEpirationTime() const
452{
453 if (m_parameters.timeToLiveUserInteraction)
454 return WallTime::now().secondsSinceEpoch() - m_parameters.timeToLiveUserInteraction.value();
455
456 if (m_operatingDates.size() >= operatingDatesWindowLong)
457 return m_operatingDates.first().secondsSinceEpoch();
458
459 return WTF::nullopt;
460}
461
462Vector<OperatingDate> ResourceLoadStatisticsStore::mergeOperatingDates(const Vector<OperatingDate>& existingDates, Vector<OperatingDate>&& newDates)
463{
464 if (existingDates.isEmpty())
465 return WTFMove(newDates);
466
467 Vector<OperatingDate> mergedDates(existingDates.size() + newDates.size());
468
469 // Merge the two sorted vectors of dates.
470 std::merge(existingDates.begin(), existingDates.end(), newDates.begin(), newDates.end(), mergedDates.begin());
471 // Remove duplicate dates.
472 removeRepeatedElements(mergedDates);
473
474 // Drop old dates until the Vector size reaches operatingDatesWindowLong.
475 while (mergedDates.size() > operatingDatesWindowLong)
476 mergedDates.remove(0);
477
478 return mergedDates;
479}
480
481void ResourceLoadStatisticsStore::mergeOperatingDates(Vector<OperatingDate>&& newDates)
482{
483 m_operatingDates = mergeOperatingDates(m_operatingDates, WTFMove(newDates));
484}
485
486void ResourceLoadStatisticsStore::includeTodayAsOperatingDateIfNecessary()
487{
488 ASSERT(!RunLoop::isMain());
489
490 auto today = OperatingDate::today();
491 if (!m_operatingDates.isEmpty() && today <= m_operatingDates.last())
492 return;
493
494 while (m_operatingDates.size() >= operatingDatesWindowLong)
495 m_operatingDates.remove(0);
496
497 m_operatingDates.append(today);
498}
499
500bool ResourceLoadStatisticsStore::hasStatisticsExpired(WallTime mostRecentUserInteractionTime, OperatingDatesWindow operatingDatesWindow) const
501{
502 ASSERT(!RunLoop::isMain());
503
504 unsigned operatingDatesWindowInDays = (operatingDatesWindow == OperatingDatesWindow::Long ? operatingDatesWindowLong : operatingDatesWindowShort);
505 if (m_operatingDates.size() >= operatingDatesWindowInDays) {
506 if (OperatingDate::fromWallTime(mostRecentUserInteractionTime) < m_operatingDates.first())
507 return true;
508 }
509
510 // If we don't meet the real criteria for an expired statistic, check the user setting for a tighter restriction (mainly for testing).
511 if (m_parameters.timeToLiveUserInteraction) {
512 if (WallTime::now() > mostRecentUserInteractionTime + m_parameters.timeToLiveUserInteraction.value())
513 return true;
514 }
515
516 return false;
517}
518
519bool ResourceLoadStatisticsStore::hasStatisticsExpired(const ResourceLoadStatistics& resourceStatistic, OperatingDatesWindow operatingDatesWindow) const
520{
521 return hasStatisticsExpired(resourceStatistic.mostRecentUserInteractionTime, operatingDatesWindow);
522}
523
524void ResourceLoadStatisticsStore::setMaxStatisticsEntries(size_t maximumEntryCount)
525{
526 ASSERT(!RunLoop::isMain());
527
528 m_parameters.maxStatisticsEntries = maximumEntryCount;
529}
530
531void ResourceLoadStatisticsStore::setPruneEntriesDownTo(size_t pruneTargetCount)
532{
533 ASSERT(!RunLoop::isMain());
534
535 m_parameters.pruneEntriesDownTo = pruneTargetCount;
536}
537
538void ResourceLoadStatisticsStore::resetParametersToDefaultValues()
539{
540 ASSERT(!RunLoop::isMain());
541
542 m_parameters = { };
543}
544
545void ResourceLoadStatisticsStore::logTestingEvent(const String& event)
546{
547 ASSERT(!RunLoop::isMain());
548
549 RunLoop::main().dispatch([store = makeRef(m_store), event = event.isolatedCopy()] {
550 store->logTestingEvent(event);
551 });
552}
553
554void ResourceLoadStatisticsStore::removeAllStorageAccess(CompletionHandler<void()>&& completionHandler)
555{
556 ASSERT(!RunLoop::isMain());
557 RunLoop::main().dispatch([store = makeRef(m_store), completionHandler = WTFMove(completionHandler)]() mutable {
558 store->removeAllStorageAccess([store = store.copyRef(), completionHandler = WTFMove(completionHandler)]() mutable {
559 store->statisticsQueue().dispatch([completionHandler = WTFMove(completionHandler)]() mutable {
560 completionHandler();
561 });
562 });
563 });
564}
565
566void ResourceLoadStatisticsStore::didCreateNetworkProcess()
567{
568 ASSERT(!RunLoop::isMain());
569
570 updateCookieBlocking([]() { });
571 updateCacheMaxAgeCap();
572 updateClientSideCookiesAgeCap();
573}
574
575void ResourceLoadStatisticsStore::debugLogDomainsInBatches(const char* action, const Vector<RegistrableDomain>& domains)
576{
577#if !RELEASE_LOG_DISABLED
578 static const auto maxNumberOfDomainsInOneLogStatement = 50;
579 if (domains.isEmpty())
580 return;
581
582 if (domains.size() <= maxNumberOfDomainsInOneLogStatement) {
583 RELEASE_LOG_INFO(ResourceLoadStatisticsDebug, "About to %{public}s cookies in third-party contexts for: %{public}s.", action, domainsToString(domains).utf8().data());
584 return;
585 }
586
587 Vector<RegistrableDomain> batch;
588 batch.reserveInitialCapacity(maxNumberOfDomainsInOneLogStatement);
589 auto batchNumber = 1;
590 unsigned numberOfBatches = std::ceil(domains.size() / static_cast<float>(maxNumberOfDomainsInOneLogStatement));
591
592 for (auto& domain : domains) {
593 if (batch.size() == maxNumberOfDomainsInOneLogStatement) {
594 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());
595 batch.shrink(0);
596 ++batchNumber;
597 }
598 batch.append(domain);
599 }
600 if (!batch.isEmpty())
601 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());
602#else
603 UNUSED_PARAM(action);
604 UNUSED_PARAM(domains);
605#endif
606}
607
608} // namespace WebKit
609
610#endif
611