Storage Access API: Make document.hasStorageAccess() retrieve current status from...
[WebKit-https.git] / Source / WebKit / UIProcess / WebResourceLoadStatisticsStore.cpp
1 /*
2  * Copyright (C) 2016-2017 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 "WebResourceLoadStatisticsStore.h"
28
29 #include "Logging.h"
30 #include "PluginProcessManager.h"
31 #include "PluginProcessProxy.h"
32 #include "WebProcessMessages.h"
33 #include "WebProcessProxy.h"
34 #include "WebResourceLoadStatisticsStoreMessages.h"
35 #include "WebResourceLoadStatisticsTelemetry.h"
36 #include "WebsiteDataFetchOption.h"
37 #include "WebsiteDataStore.h"
38 #include <WebCore/KeyedCoding.h>
39 #include <WebCore/ResourceLoadStatistics.h>
40 #include <wtf/CrossThreadCopier.h>
41 #include <wtf/DateMath.h>
42 #include <wtf/MathExtras.h>
43 #include <wtf/NeverDestroyed.h>
44
45 using namespace WebCore;
46
47 namespace WebKit {
48
49 constexpr unsigned operatingDatesWindow { 30 };
50 constexpr unsigned statisticsModelVersion { 10 };
51 constexpr unsigned maxImportance { 3 };
52
53 template<typename T> static inline String isolatedPrimaryDomain(const T& value)
54 {
55     return ResourceLoadStatistics::primaryDomain(value).isolatedCopy();
56 }
57
58 const OptionSet<WebsiteDataType>& WebResourceLoadStatisticsStore::monitoredDataTypes()
59 {
60     static NeverDestroyed<OptionSet<WebsiteDataType>> dataTypes(std::initializer_list<WebsiteDataType>({
61         WebsiteDataType::Cookies,
62         WebsiteDataType::IndexedDBDatabases,
63         WebsiteDataType::LocalStorage,
64         WebsiteDataType::MediaKeys,
65         WebsiteDataType::OfflineWebApplicationCache,
66 #if ENABLE(NETSCAPE_PLUGIN_API)
67         WebsiteDataType::PlugInData,
68 #endif
69         WebsiteDataType::SearchFieldRecentSearches,
70         WebsiteDataType::SessionStorage,
71         WebsiteDataType::WebSQLDatabases,
72     }));
73
74     ASSERT(RunLoop::isMain());
75
76     return dataTypes;
77 }
78
79 class OperatingDate {
80 public:
81     OperatingDate() = default;
82
83     static OperatingDate fromWallTime(WallTime time)
84     {
85         double ms = time.secondsSinceEpoch().milliseconds();
86         int year = msToYear(ms);
87         int yearDay = dayInYear(ms, year);
88         int month = monthFromDayInYear(yearDay, isLeapYear(year));
89         int monthDay = dayInMonthFromDayInYear(yearDay, isLeapYear(year));
90
91         return OperatingDate { year, month, monthDay };
92     }
93
94     static OperatingDate today()
95     {
96         return OperatingDate::fromWallTime(WallTime::now());
97     }
98
99     Seconds secondsSinceEpoch() const
100     {
101         return Seconds { dateToDaysFrom1970(m_year, m_month, m_monthDay) * secondsPerDay };
102     }
103
104     bool operator==(const OperatingDate& other) const
105     {
106         return m_monthDay == other.m_monthDay && m_month == other.m_month && m_year == other.m_year;
107     }
108
109     bool operator<(const OperatingDate& other) const
110     {
111         return secondsSinceEpoch() < other.secondsSinceEpoch();
112     }
113
114     bool operator<=(const OperatingDate& other) const
115     {
116         return secondsSinceEpoch() <= other.secondsSinceEpoch();
117     }
118
119 private:
120     OperatingDate(int year, int month, int monthDay)
121         : m_year(year)
122         , m_month(month)
123         , m_monthDay(monthDay)
124     { }
125
126     int m_year { 0 };
127     int m_month { 0 }; // [0, 11].
128     int m_monthDay { 0 }; // [1, 31].
129 };
130
131 static Vector<OperatingDate> mergeOperatingDates(const Vector<OperatingDate>& existingDates, Vector<OperatingDate>&& newDates)
132 {
133     if (existingDates.isEmpty())
134         return WTFMove(newDates);
135
136     Vector<OperatingDate> mergedDates(existingDates.size() + newDates.size());
137
138     // Merge the two sorted vectors of dates.
139     std::merge(existingDates.begin(), existingDates.end(), newDates.begin(), newDates.end(), mergedDates.begin());
140     // Remove duplicate dates.
141     removeRepeatedElements(mergedDates);
142
143     // Drop old dates until the Vector size reaches operatingDatesWindow.
144     while (mergedDates.size() > operatingDatesWindow)
145         mergedDates.remove(0);
146
147     return mergedDates;
148 }
149
150 WebResourceLoadStatisticsStore::WebResourceLoadStatisticsStore(const String& resourceLoadStatisticsDirectory, Function<void(const String&)>&& testingCallback, UpdatePrevalentDomainsToPartitionOrBlockCookiesHandler&& updatePrevalentDomainsToPartitionOrBlockCookiesHandler, HasStorageAccessForPrevalentDomainsHandler&& hasStorageAccessForPrevalentDomainsHandler, UpdateStorageAccessForPrevalentDomainsHandler&& updateStorageAccessForPrevalentDomainsHandler, RemovePrevalentDomainsHandler&& removeDomainsHandler)
151     : m_statisticsQueue(WorkQueue::create("WebResourceLoadStatisticsStore Process Data Queue", WorkQueue::Type::Serial, WorkQueue::QOS::Utility))
152     , m_persistentStorage(*this, resourceLoadStatisticsDirectory)
153     , m_updatePrevalentDomainsToPartitionOrBlockCookiesHandler(WTFMove(updatePrevalentDomainsToPartitionOrBlockCookiesHandler))
154     , m_hasStorageAccessForPrevalentDomainsHandler(WTFMove(hasStorageAccessForPrevalentDomainsHandler))
155     , m_updateStorageAccessForPrevalentDomainsHandler(WTFMove(updateStorageAccessForPrevalentDomainsHandler))
156     , m_removeDomainsHandler(WTFMove(removeDomainsHandler))
157     , m_dailyTasksTimer(RunLoop::main(), this, &WebResourceLoadStatisticsStore::performDailyTasks)
158     , m_statisticsTestingCallback(WTFMove(testingCallback))
159 {
160     ASSERT(RunLoop::isMain());
161
162 #if PLATFORM(COCOA)
163     registerUserDefaultsIfNeeded();
164 #endif
165
166     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
167         m_persistentStorage.initialize();
168         includeTodayAsOperatingDateIfNecessary();
169     });
170
171     m_statisticsQueue->dispatchAfter(5_s, [this, protectedThis = makeRef(*this)] {
172         if (m_parameters.shouldSubmitTelemetry)
173             WebResourceLoadStatisticsTelemetry::calculateAndSubmit(*this);
174     });
175
176     m_dailyTasksTimer.startRepeating(24_h);
177 }
178
179 WebResourceLoadStatisticsStore::~WebResourceLoadStatisticsStore()
180 {
181     m_persistentStorage.finishAllPendingWorkSynchronously();
182 }
183     
184 void WebResourceLoadStatisticsStore::removeDataRecords()
185 {
186     ASSERT(!RunLoop::isMain());
187     
188     if (!shouldRemoveDataRecords())
189         return;
190
191 #if ENABLE(NETSCAPE_PLUGIN_API)
192     m_activePluginTokens.clear();
193     for (auto plugin : PluginProcessManager::singleton().pluginProcesses())
194         m_activePluginTokens.add(plugin->pluginProcessToken());
195 #endif
196
197     auto prevalentResourceDomains = topPrivatelyControlledDomainsToRemoveWebsiteDataFor();
198     if (prevalentResourceDomains.isEmpty())
199         return;
200     
201     setDataRecordsBeingRemoved(true);
202
203     RunLoop::main().dispatch([prevalentResourceDomains = crossThreadCopy(prevalentResourceDomains), this, protectedThis = makeRef(*this)] () mutable {
204         WebProcessProxy::deleteWebsiteDataForTopPrivatelyControlledDomainsInAllPersistentDataStores(WebResourceLoadStatisticsStore::monitoredDataTypes(), WTFMove(prevalentResourceDomains), m_parameters.shouldNotifyPagesWhenDataRecordsWereScanned, [this, protectedThis = WTFMove(protectedThis)](const HashSet<String>& domainsWithDeletedWebsiteData) mutable {
205             m_statisticsQueue->dispatch([this, protectedThis = WTFMove(protectedThis), topDomains = crossThreadCopy(domainsWithDeletedWebsiteData)] () mutable {
206                 for (auto& prevalentResourceDomain : topDomains) {
207                     auto& statistic = ensureResourceStatisticsForPrimaryDomain(prevalentResourceDomain);
208                     ++statistic.dataRecordsRemoved;
209                 }
210                 setDataRecordsBeingRemoved(false);
211             });
212         });
213     });
214 }
215
216 void WebResourceLoadStatisticsStore::scheduleStatisticsAndDataRecordsProcessing()
217 {
218     ASSERT(RunLoop::isMain());
219     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
220         processStatisticsAndDataRecords();
221     });
222 }
223
224 void WebResourceLoadStatisticsStore::processStatisticsAndDataRecords()
225 {
226     ASSERT(!RunLoop::isMain());
227
228     if (m_parameters.shouldClassifyResourcesBeforeDataRecordsRemoval) {
229         for (auto& resourceStatistic : m_resourceStatisticsMap.values()) {
230             if (!resourceStatistic.isPrevalentResource && m_resourceLoadStatisticsClassifier.hasPrevalentResourceCharacteristics(resourceStatistic))
231                 resourceStatistic.isPrevalentResource = true;
232         }
233     }
234     removeDataRecords();
235
236     pruneStatisticsIfNeeded();
237
238     if (m_parameters.shouldNotifyPagesWhenDataRecordsWereScanned) {
239         RunLoop::main().dispatch([] {
240             WebProcessProxy::notifyPageStatisticsAndDataRecordsProcessed();
241         });
242     }
243
244     m_persistentStorage.scheduleOrWriteMemoryStore(ResourceLoadStatisticsPersistentStorage::ForceImmediateWrite::No);
245 }
246
247 void WebResourceLoadStatisticsStore::resourceLoadStatisticsUpdated(Vector<WebCore::ResourceLoadStatistics>&& origins)
248 {
249     ASSERT(!RunLoop::isMain());
250
251     mergeStatistics(WTFMove(origins));
252     // Fire before processing statistics to propagate user interaction as fast as possible to the network process.
253     updateCookiePartitioning();
254     processStatisticsAndDataRecords();
255 }
256
257 void WebResourceLoadStatisticsStore::hasStorageAccess(String&& subFrameHost, String&& topFrameHost, uint64_t frameID, uint64_t pageID, WTF::CompletionHandler<void (bool)>&& callback)
258 {
259     ASSERT(subFrameHost != topFrameHost);
260     ASSERT(RunLoop::isMain());
261
262     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), subFramePrimaryDomain = isolatedPrimaryDomain(subFrameHost), topFramePrimaryDomain = isolatedPrimaryDomain(topFrameHost), frameID, pageID, callback = WTFMove(callback)] () mutable {
263         
264         auto& subFrameStatistic = ensureResourceStatisticsForPrimaryDomain(subFramePrimaryDomain);
265         if (shouldBlockCookies(subFrameStatistic)) {
266             callback(false);
267             return;
268         }
269
270         if (!shouldPartitionCookies(subFrameStatistic)) {
271             callback(true);
272             return;
273         }
274
275         m_hasStorageAccessForPrevalentDomainsHandler(subFramePrimaryDomain, topFramePrimaryDomain, frameID, pageID, WTFMove(callback));
276     });
277 }
278
279 void WebResourceLoadStatisticsStore::requestStorageAccess(String&& subFrameHost, String&& topFrameHost, uint64_t frameID, uint64_t pageID, WTF::CompletionHandler<void (bool)>&& callback)
280 {
281     ASSERT(subFrameHost != topFrameHost);
282     ASSERT(RunLoop::isMain());
283
284     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), subFramePrimaryDomain = isolatedPrimaryDomain(subFrameHost), topFramePrimaryDomain = isolatedPrimaryDomain(topFrameHost), frameID, pageID, callback = WTFMove(callback)] () mutable {
285
286         auto& subFrameStatistic = ensureResourceStatisticsForPrimaryDomain(subFramePrimaryDomain);
287         if (shouldBlockCookies(subFrameStatistic)) {
288             callback(false);
289             return;
290         }
291         
292         if (!shouldPartitionCookies(subFrameStatistic)) {
293             callback(true);
294             return;
295         }
296         
297         m_updateStorageAccessForPrevalentDomainsHandler(subFramePrimaryDomain, topFramePrimaryDomain, frameID, pageID, true, WTFMove(callback));
298     });
299 }
300     
301 void WebResourceLoadStatisticsStore::grandfatherExistingWebsiteData()
302 {
303     ASSERT(!RunLoop::isMain());
304
305     RunLoop::main().dispatch([this, protectedThis = makeRef(*this)] () mutable {
306         // FIXME: This method being a static call on WebProcessProxy is wrong.
307         // It should be on the data store that this object belongs to.
308         WebProcessProxy::topPrivatelyControlledDomainsWithWebsiteData(WebResourceLoadStatisticsStore::monitoredDataTypes(), m_parameters.shouldNotifyPagesWhenDataRecordsWereScanned, [this, protectedThis = WTFMove(protectedThis)] (HashSet<String>&& topPrivatelyControlledDomainsWithWebsiteData) mutable {
309             m_statisticsQueue->dispatch([this, protectedThis = WTFMove(protectedThis), topDomains = crossThreadCopy(topPrivatelyControlledDomainsWithWebsiteData)] () mutable {
310                 for (auto& topPrivatelyControlledDomain : topDomains) {
311                     auto& statistic = ensureResourceStatisticsForPrimaryDomain(topPrivatelyControlledDomain);
312                     statistic.grandfathered = true;
313                 }
314                 m_endOfGrandfatheringTimestamp = WallTime::now() + m_parameters.grandfatheringTime;
315                 m_persistentStorage.scheduleOrWriteMemoryStore(ResourceLoadStatisticsPersistentStorage::ForceImmediateWrite::Yes);
316             });
317
318             logTestingEvent(ASCIILiteral("Grandfathered"));
319         });
320     });
321 }
322     
323 void WebResourceLoadStatisticsStore::processWillOpenConnection(WebProcessProxy&, IPC::Connection& connection)
324 {
325     connection.addWorkQueueMessageReceiver(Messages::WebResourceLoadStatisticsStore::messageReceiverName(), m_statisticsQueue.get(), this);
326 }
327
328 void WebResourceLoadStatisticsStore::processDidCloseConnection(WebProcessProxy&, IPC::Connection& connection)
329 {
330     connection.removeWorkQueueMessageReceiver(Messages::WebResourceLoadStatisticsStore::messageReceiverName());
331 }
332
333 void WebResourceLoadStatisticsStore::applicationWillTerminate()
334 {
335     m_persistentStorage.finishAllPendingWorkSynchronously();
336 }
337
338 void WebResourceLoadStatisticsStore::performDailyTasks()
339 {
340     ASSERT(RunLoop::isMain());
341
342     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
343         includeTodayAsOperatingDateIfNecessary();
344     });
345     if (m_parameters.shouldSubmitTelemetry)
346         submitTelemetry();
347 }
348
349 void WebResourceLoadStatisticsStore::submitTelemetry()
350 {
351     ASSERT(RunLoop::isMain());
352     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
353         WebResourceLoadStatisticsTelemetry::calculateAndSubmit(*this);
354     });
355 }
356
357 void WebResourceLoadStatisticsStore::logUserInteraction(const URL& url)
358 {
359     if (url.isBlankURL() || url.isEmpty())
360         return;
361
362     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url)] {
363         auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
364         statistics.hadUserInteraction = true;
365         statistics.mostRecentUserInteractionTime = WallTime::now();
366
367         updateCookiePartitioningForDomains({ }, { }, { primaryDomain }, ShouldClearFirst::No);
368     });
369 }
370
371 void WebResourceLoadStatisticsStore::logNonRecentUserInteraction(const URL& url)
372 {
373     if (url.isBlankURL() || url.isEmpty())
374         return;
375     
376     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url)] {
377         auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
378         statistics.hadUserInteraction = true;
379         statistics.mostRecentUserInteractionTime = WallTime::now() - (m_parameters.timeToLiveCookiePartitionFree + Seconds::fromHours(1));
380
381         updateCookiePartitioningForDomains({ primaryDomain }, { }, { }, ShouldClearFirst::No);
382     });
383 }
384
385 void WebResourceLoadStatisticsStore::clearUserInteraction(const URL& url)
386 {
387     if (url.isBlankURL() || url.isEmpty())
388         return;
389
390     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url)] {
391         auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
392         statistics.hadUserInteraction = false;
393         statistics.mostRecentUserInteractionTime = { };
394     });
395 }
396
397 void WebResourceLoadStatisticsStore::hasHadUserInteraction(const URL& url, WTF::Function<void (bool)>&& completionHandler)
398 {
399     if (url.isBlankURL() || url.isEmpty()) {
400         completionHandler(false);
401         return;
402     }
403
404     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url), completionHandler = WTFMove(completionHandler)] () mutable {
405         auto mapEntry = m_resourceStatisticsMap.find(primaryDomain);
406         bool hadUserInteraction = mapEntry == m_resourceStatisticsMap.end() ? false: hasHadUnexpiredRecentUserInteraction(mapEntry->value);
407         RunLoop::main().dispatch([hadUserInteraction, completionHandler = WTFMove(completionHandler)] {
408             completionHandler(hadUserInteraction);
409         });
410     });
411 }
412
413 void WebResourceLoadStatisticsStore::setLastSeen(const URL& url, Seconds seconds)
414 {
415     if (url.isBlankURL() || url.isEmpty())
416         return;
417     
418     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url), seconds] {
419         auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
420         statistics.lastSeen = WallTime::fromRawSeconds(seconds.seconds());
421     });
422 }
423     
424 void WebResourceLoadStatisticsStore::setPrevalentResource(const URL& url)
425 {
426     if (url.isBlankURL() || url.isEmpty())
427         return;
428
429     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url)] {
430         auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
431         statistics.isPrevalentResource = true;
432     });
433 }
434
435 void WebResourceLoadStatisticsStore::isPrevalentResource(const URL& url, WTF::Function<void (bool)>&& completionHandler)
436 {
437     if (url.isBlankURL() || url.isEmpty()) {
438         completionHandler(false);
439         return;
440     }
441
442     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url), completionHandler = WTFMove(completionHandler)] () mutable {
443         auto mapEntry = m_resourceStatisticsMap.find(primaryDomain);
444         bool isPrevalentResource = mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.isPrevalentResource;
445         RunLoop::main().dispatch([isPrevalentResource, completionHandler = WTFMove(completionHandler)] {
446             completionHandler(isPrevalentResource);
447         });
448     });
449 }
450
451 void WebResourceLoadStatisticsStore::isRegisteredAsSubFrameUnder(const URL& subFrame, const URL& topFrame, WTF::Function<void (bool)>&& completionHandler)
452 {
453     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), subFramePrimaryDomain = isolatedPrimaryDomain(subFrame), topFramePrimaryDomain = isolatedPrimaryDomain(topFrame), completionHandler = WTFMove(completionHandler)] () mutable {
454         auto mapEntry = m_resourceStatisticsMap.find(subFramePrimaryDomain);
455         bool isRegisteredAsSubFrameUnder = mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.subframeUnderTopFrameOrigins.contains(topFramePrimaryDomain);
456         RunLoop::main().dispatch([isRegisteredAsSubFrameUnder, completionHandler = WTFMove(completionHandler)] {
457             completionHandler(isRegisteredAsSubFrameUnder);
458         });
459     });
460 }
461
462 void WebResourceLoadStatisticsStore::isRegisteredAsRedirectingTo(const URL& hostRedirectedFrom, const URL& hostRedirectedTo, WTF::Function<void (bool)>&& completionHandler)
463 {
464     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), hostRedirectedFromPrimaryDomain = isolatedPrimaryDomain(hostRedirectedFrom), hostRedirectedToPrimaryDomain = isolatedPrimaryDomain(hostRedirectedTo), completionHandler = WTFMove(completionHandler)] () mutable {
465         auto mapEntry = m_resourceStatisticsMap.find(hostRedirectedFromPrimaryDomain);
466         bool isRegisteredAsRedirectingTo = mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.subresourceUniqueRedirectsTo.contains(hostRedirectedToPrimaryDomain);
467         RunLoop::main().dispatch([isRegisteredAsRedirectingTo, completionHandler = WTFMove(completionHandler)] {
468             completionHandler(isRegisteredAsRedirectingTo);
469         });
470     });
471 }
472
473 void WebResourceLoadStatisticsStore::clearPrevalentResource(const URL& url)
474 {
475     if (url.isBlankURL() || url.isEmpty())
476         return;
477
478     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url)] {
479         auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
480         statistics.isPrevalentResource = false;
481     });
482 }
483
484 void WebResourceLoadStatisticsStore::setGrandfathered(const URL& url, bool value)
485 {
486     if (url.isBlankURL() || url.isEmpty())
487         return;
488
489     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url), value] {
490         auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
491         statistics.grandfathered = value;
492     });
493 }
494
495 void WebResourceLoadStatisticsStore::isGrandfathered(const URL& url, WTF::Function<void (bool)>&& completionHandler)
496 {
497     if (url.isBlankURL() || url.isEmpty()) {
498         completionHandler(false);
499         return;
500     }
501
502     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler), primaryDomain = isolatedPrimaryDomain(url)] () mutable {
503         auto mapEntry = m_resourceStatisticsMap.find(primaryDomain);
504         bool isGrandFathered = mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.grandfathered;
505         RunLoop::main().dispatch([isGrandFathered, completionHandler = WTFMove(completionHandler)] {
506             completionHandler(isGrandFathered);
507         });
508     });
509 }
510
511 void WebResourceLoadStatisticsStore::setSubframeUnderTopFrameOrigin(const URL& subframe, const URL& topFrame)
512 {
513     if (subframe.isBlankURL() || subframe.isEmpty() || topFrame.isBlankURL() || topFrame.isEmpty())
514         return;
515
516     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryTopFrameDomain = isolatedPrimaryDomain(topFrame), primarySubFrameDomain = isolatedPrimaryDomain(subframe)] {
517         auto& statistics = ensureResourceStatisticsForPrimaryDomain(primarySubFrameDomain);
518         statistics.subframeUnderTopFrameOrigins.add(primaryTopFrameDomain);
519     });
520 }
521
522 void WebResourceLoadStatisticsStore::setSubresourceUnderTopFrameOrigin(const URL& subresource, const URL& topFrame)
523 {
524     if (subresource.isBlankURL() || subresource.isEmpty() || topFrame.isBlankURL() || topFrame.isEmpty())
525         return;
526
527     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryTopFrameDomain = isolatedPrimaryDomain(topFrame), primarySubresourceDomain = isolatedPrimaryDomain(subresource)] {
528         auto& statistics = ensureResourceStatisticsForPrimaryDomain(primarySubresourceDomain);
529         statistics.subresourceUnderTopFrameOrigins.add(primaryTopFrameDomain);
530     });
531 }
532
533 void WebResourceLoadStatisticsStore::setSubresourceUniqueRedirectTo(const URL& subresource, const URL& hostNameRedirectedTo)
534 {
535     if (subresource.isBlankURL() || subresource.isEmpty() || hostNameRedirectedTo.isBlankURL() || hostNameRedirectedTo.isEmpty())
536         return;
537
538     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryRedirectDomain = isolatedPrimaryDomain(hostNameRedirectedTo), primarySubresourceDomain = isolatedPrimaryDomain(subresource)] {
539         auto& statistics = ensureResourceStatisticsForPrimaryDomain(primarySubresourceDomain);
540         statistics.subresourceUniqueRedirectsTo.add(primaryRedirectDomain);
541     });
542 }
543
544 void WebResourceLoadStatisticsStore::scheduleCookiePartitioningUpdate()
545 {
546     // Helper function used by testing system. Should only be called from the main thread.
547     ASSERT(RunLoop::isMain());
548
549     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
550         updateCookiePartitioning();
551     });
552 }
553
554 void WebResourceLoadStatisticsStore::scheduleCookiePartitioningUpdateForDomains(const Vector<String>& domainsToPartition, const Vector<String>& domainsToBlock, const Vector<String>& domainsToNeitherPartitionNorBlock, ShouldClearFirst shouldClearFirst)
555 {
556     // Helper function used by testing system. Should only be called from the main thread.
557     ASSERT(RunLoop::isMain());
558     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), domainsToPartition = crossThreadCopy(domainsToPartition), domainsToBlock = crossThreadCopy(domainsToBlock), domainsToNeitherPartitionNorBlock = crossThreadCopy(domainsToNeitherPartitionNorBlock), shouldClearFirst] {
559         updateCookiePartitioningForDomains(domainsToPartition, domainsToBlock, domainsToNeitherPartitionNorBlock, shouldClearFirst);
560     });
561 }
562
563 void WebResourceLoadStatisticsStore::scheduleClearPartitioningStateForDomains(const Vector<String>& domains)
564 {
565     // Helper function used by testing system. Should only be called from the main thread.
566     ASSERT(RunLoop::isMain());
567     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), domains = crossThreadCopy(domains)] {
568         clearPartitioningStateForDomains(domains);
569     });
570 }
571
572 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
573 void WebResourceLoadStatisticsStore::scheduleCookiePartitioningStateReset()
574 {
575     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
576         resetCookiePartitioningState();
577     });
578 }
579 #endif
580
581 void WebResourceLoadStatisticsStore::scheduleClearInMemory()
582 {
583     ASSERT(RunLoop::isMain());
584     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
585         clearInMemory();
586     });
587 }
588
589 void WebResourceLoadStatisticsStore::scheduleClearInMemoryAndPersistent(ShouldGrandfather shouldGrandfather)
590 {
591     ASSERT(RunLoop::isMain());
592     m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), shouldGrandfather] {
593         clearInMemory();
594         m_persistentStorage.clear();
595         
596         if (shouldGrandfather == ShouldGrandfather::Yes)
597             grandfatherExistingWebsiteData();
598     });
599 }
600
601 void WebResourceLoadStatisticsStore::scheduleClearInMemoryAndPersistent(std::chrono::system_clock::time_point modifiedSince, ShouldGrandfather shouldGrandfather)
602 {
603     // For now, be conservative and clear everything regardless of modifiedSince.
604     UNUSED_PARAM(modifiedSince);
605     scheduleClearInMemoryAndPersistent(shouldGrandfather);
606 }
607
608 void WebResourceLoadStatisticsStore::setTimeToLiveUserInteraction(Seconds seconds)
609 {
610     ASSERT(seconds >= 0_s);
611     m_parameters.timeToLiveUserInteraction = seconds;
612 }
613
614 void WebResourceLoadStatisticsStore::setTimeToLiveCookiePartitionFree(Seconds seconds)
615 {
616     ASSERT(seconds >= 0_s);
617     m_parameters.timeToLiveCookiePartitionFree = seconds;
618 }
619
620 void WebResourceLoadStatisticsStore::setMinimumTimeBetweenDataRecordsRemoval(Seconds seconds)
621 {
622     ASSERT(seconds >= 0_s);
623     m_parameters.minimumTimeBetweenDataRecordsRemoval = seconds;
624 }
625
626 void WebResourceLoadStatisticsStore::setGrandfatheringTime(Seconds seconds)
627 {
628     ASSERT(seconds >= 0_s);
629     m_parameters.grandfatheringTime = seconds;
630 }
631
632 bool WebResourceLoadStatisticsStore::shouldRemoveDataRecords() const
633 {
634     ASSERT(!RunLoop::isMain());
635     if (m_dataRecordsBeingRemoved)
636         return false;
637
638 #if ENABLE(NETSCAPE_PLUGIN_API)
639     for (auto plugin : PluginProcessManager::singleton().pluginProcesses()) {
640         if (!m_activePluginTokens.contains(plugin->pluginProcessToken()))
641             return true;
642     }
643 #endif
644
645     return !m_lastTimeDataRecordsWereRemoved || MonotonicTime::now() >= (m_lastTimeDataRecordsWereRemoved + m_parameters.minimumTimeBetweenDataRecordsRemoval);
646 }
647
648 void WebResourceLoadStatisticsStore::setDataRecordsBeingRemoved(bool value)
649 {
650     ASSERT(!RunLoop::isMain());
651     m_dataRecordsBeingRemoved = value;
652     if (m_dataRecordsBeingRemoved)
653         m_lastTimeDataRecordsWereRemoved = MonotonicTime::now();
654 }
655
656 ResourceLoadStatistics& WebResourceLoadStatisticsStore::ensureResourceStatisticsForPrimaryDomain(const String& primaryDomain)
657 {
658     ASSERT(!RunLoop::isMain());
659     return m_resourceStatisticsMap.ensure(primaryDomain, [&primaryDomain] {
660         return ResourceLoadStatistics(primaryDomain);
661     }).iterator->value;
662 }
663
664 std::unique_ptr<KeyedEncoder> WebResourceLoadStatisticsStore::createEncoderFromData() const
665 {
666     ASSERT(!RunLoop::isMain());
667     auto encoder = KeyedEncoder::encoder();
668     encoder->encodeUInt32("version", statisticsModelVersion);
669     encoder->encodeDouble("endOfGrandfatheringTimestamp", m_endOfGrandfatheringTimestamp.secondsSinceEpoch().value());
670
671     encoder->encodeObjects("browsingStatistics", m_resourceStatisticsMap.begin(), m_resourceStatisticsMap.end(), [](KeyedEncoder& encoderInner, const auto& origin) {
672         origin.value.encode(encoderInner);
673     });
674
675     encoder->encodeObjects("operatingDates", m_operatingDates.begin(), m_operatingDates.end(), [](KeyedEncoder& encoderInner, OperatingDate date) {
676         encoderInner.encodeDouble("date", date.secondsSinceEpoch().value());
677     });
678
679     return encoder;
680 }
681
682 void WebResourceLoadStatisticsStore::mergeWithDataFromDecoder(KeyedDecoder& decoder)
683 {
684     ASSERT(!RunLoop::isMain());
685
686     unsigned versionOnDisk;
687     if (!decoder.decodeUInt32("version", versionOnDisk))
688         return;
689
690     if (versionOnDisk != statisticsModelVersion)
691         return;
692
693     double endOfGrandfatheringTimestamp;
694     if (decoder.decodeDouble("endOfGrandfatheringTimestamp", endOfGrandfatheringTimestamp))
695         m_endOfGrandfatheringTimestamp = WallTime::fromRawSeconds(endOfGrandfatheringTimestamp);
696     else
697         m_endOfGrandfatheringTimestamp = { };
698
699     Vector<ResourceLoadStatistics> loadedStatistics;
700     bool succeeded = decoder.decodeObjects("browsingStatistics", loadedStatistics, [](KeyedDecoder& decoderInner, ResourceLoadStatistics& statistics) {
701         return statistics.decode(decoderInner);
702     });
703
704     if (!succeeded)
705         return;
706
707     mergeStatistics(WTFMove(loadedStatistics));
708     updateCookiePartitioning();
709
710     Vector<OperatingDate> operatingDates;
711     succeeded = decoder.decodeObjects("operatingDates", operatingDates, [](KeyedDecoder& decoder, OperatingDate& date) {
712         double value;
713         if (!decoder.decodeDouble("date", value))
714             return false;
715
716         date = OperatingDate::fromWallTime(WallTime::fromRawSeconds(value));
717         return true;
718     });
719
720     if (!succeeded)
721         return;
722
723     m_operatingDates = mergeOperatingDates(m_operatingDates, WTFMove(operatingDates));
724 }
725
726 void WebResourceLoadStatisticsStore::clearInMemory()
727 {
728     ASSERT(!RunLoop::isMain());
729     m_resourceStatisticsMap.clear();
730     m_operatingDates.clear();
731
732     updateCookiePartitioningForDomains({ }, { }, { }, ShouldClearFirst::Yes);
733 }
734
735 void WebResourceLoadStatisticsStore::mergeStatistics(Vector<ResourceLoadStatistics>&& statistics)
736 {
737     ASSERT(!RunLoop::isMain());
738     for (auto& statistic : statistics) {
739         auto result = m_resourceStatisticsMap.ensure(statistic.highLevelDomain, [&statistic] {
740             return WTFMove(statistic);
741         });
742         if (!result.isNewEntry)
743             result.iterator->value.merge(statistic);
744     }
745 }
746
747 bool WebResourceLoadStatisticsStore::shouldPartitionCookies(const ResourceLoadStatistics& statistic) const
748 {
749     return statistic.isPrevalentResource && statistic.hadUserInteraction && WallTime::now() > statistic.mostRecentUserInteractionTime + m_parameters.timeToLiveCookiePartitionFree;
750 }
751
752 bool WebResourceLoadStatisticsStore::shouldBlockCookies(const ResourceLoadStatistics& statistic) const
753 {
754     return statistic.isPrevalentResource && !statistic.hadUserInteraction;
755 }
756
757 void WebResourceLoadStatisticsStore::updateCookiePartitioning()
758 {
759     ASSERT(!RunLoop::isMain());
760
761     Vector<String> domainsToPartition;
762     Vector<String> domainsToBlock;
763     Vector<String> domainsToNeitherPartitionNorBlock;
764     for (auto& resourceStatistic : m_resourceStatisticsMap.values()) {
765
766         bool shouldPartition = shouldPartitionCookies(resourceStatistic);
767         bool shouldBlock = shouldBlockCookies(resourceStatistic);
768
769         if (shouldPartition && !resourceStatistic.isMarkedForCookiePartitioning) {
770             domainsToPartition.append(resourceStatistic.highLevelDomain);
771             resourceStatistic.isMarkedForCookiePartitioning = true;
772         } else if (shouldBlock && !resourceStatistic.isMarkedForCookieBlocking) {
773             domainsToBlock.append(resourceStatistic.highLevelDomain);
774             resourceStatistic.isMarkedForCookieBlocking = true;
775         } else if (!shouldPartition && !shouldBlock && resourceStatistic.isPrevalentResource) {
776             domainsToNeitherPartitionNorBlock.append(resourceStatistic.highLevelDomain);
777             resourceStatistic.isMarkedForCookiePartitioning = false;
778             resourceStatistic.isMarkedForCookieBlocking = false;
779         }
780     }
781
782     if (domainsToPartition.isEmpty() && domainsToBlock.isEmpty() && domainsToNeitherPartitionNorBlock.isEmpty())
783         return;
784
785     RunLoop::main().dispatch([this, protectedThis = makeRef(*this), domainsToPartition = crossThreadCopy(domainsToPartition), domainsToBlock = crossThreadCopy(domainsToBlock), domainsToNeitherPartitionNorBlock = crossThreadCopy(domainsToNeitherPartitionNorBlock)] () {
786         m_updatePrevalentDomainsToPartitionOrBlockCookiesHandler(domainsToPartition, domainsToBlock, domainsToNeitherPartitionNorBlock, ShouldClearFirst::No);
787     });
788 }
789
790 void WebResourceLoadStatisticsStore::updateCookiePartitioningForDomains(const Vector<String>& domainsToPartition, const Vector<String>& domainsToBlock, const Vector<String>& domainsToNeitherPartitionNorBlock, ShouldClearFirst shouldClearFirst)
791 {
792     ASSERT(!RunLoop::isMain());
793     if (domainsToPartition.isEmpty() && domainsToBlock.isEmpty() && domainsToNeitherPartitionNorBlock.isEmpty() && shouldClearFirst == ShouldClearFirst::No)
794         return;
795
796     RunLoop::main().dispatch([this, shouldClearFirst, protectedThis = makeRef(*this), domainsToPartition = crossThreadCopy(domainsToPartition), domainsToBlock = crossThreadCopy(domainsToBlock), domainsToNeitherPartitionNorBlock = crossThreadCopy(domainsToNeitherPartitionNorBlock)] () {
797         m_updatePrevalentDomainsToPartitionOrBlockCookiesHandler(domainsToPartition, domainsToBlock, domainsToNeitherPartitionNorBlock, shouldClearFirst);
798     });
799
800     if (shouldClearFirst == ShouldClearFirst::Yes)
801         resetCookiePartitioningState();
802     else {
803         for (auto& domain : domainsToNeitherPartitionNorBlock) {
804             auto& statistic = ensureResourceStatisticsForPrimaryDomain(domain);
805             statistic.isMarkedForCookiePartitioning = false;
806             statistic.isMarkedForCookieBlocking = false;
807         }
808     }
809
810     for (auto& domain : domainsToPartition)
811         ensureResourceStatisticsForPrimaryDomain(domain).isMarkedForCookiePartitioning = true;
812
813     for (auto& domain : domainsToBlock)
814         ensureResourceStatisticsForPrimaryDomain(domain).isMarkedForCookieBlocking = true;
815 }
816
817 void WebResourceLoadStatisticsStore::clearPartitioningStateForDomains(const Vector<String>& domains)
818 {
819     ASSERT(!RunLoop::isMain());
820     if (domains.isEmpty())
821         return;
822
823     RunLoop::main().dispatch([this, protectedThis = makeRef(*this), domains = crossThreadCopy(domains)] () {
824         m_removeDomainsHandler(domains);
825     });
826
827     for (auto& domain : domains) {
828         auto& statistic = ensureResourceStatisticsForPrimaryDomain(domain);
829         statistic.isMarkedForCookiePartitioning = false;
830         statistic.isMarkedForCookieBlocking = false;
831     }
832 }
833
834 void WebResourceLoadStatisticsStore::resetCookiePartitioningState()
835 {
836     ASSERT(!RunLoop::isMain());
837     for (auto& resourceStatistic : m_resourceStatisticsMap.values()) {
838         resourceStatistic.isMarkedForCookiePartitioning = false;
839         resourceStatistic.isMarkedForCookieBlocking = false;
840     }
841 }
842
843 void WebResourceLoadStatisticsStore::processStatistics(const WTF::Function<void (const ResourceLoadStatistics&)>& processFunction) const
844 {
845     ASSERT(!RunLoop::isMain());
846     for (auto& resourceStatistic : m_resourceStatisticsMap.values())
847         processFunction(resourceStatistic);
848 }
849
850 bool WebResourceLoadStatisticsStore::hasHadUnexpiredRecentUserInteraction(ResourceLoadStatistics& resourceStatistic) const
851 {
852     if (resourceStatistic.hadUserInteraction && hasStatisticsExpired(resourceStatistic)) {
853         // Drop privacy sensitive data because we no longer need it.
854         // Set timestamp to 0 so that statistics merge will know
855         // it has been reset as opposed to its default -1.
856         resourceStatistic.mostRecentUserInteractionTime = { };
857         resourceStatistic.hadUserInteraction = false;
858     }
859
860     return resourceStatistic.hadUserInteraction;
861 }
862
863 Vector<String> WebResourceLoadStatisticsStore::topPrivatelyControlledDomainsToRemoveWebsiteDataFor()
864 {
865     ASSERT(!RunLoop::isMain());
866
867     bool shouldCheckForGrandfathering = m_endOfGrandfatheringTimestamp > WallTime::now();
868     bool shouldClearGrandfathering = !shouldCheckForGrandfathering && m_endOfGrandfatheringTimestamp;
869
870     if (shouldClearGrandfathering)
871         m_endOfGrandfatheringTimestamp = { };
872
873     Vector<String> prevalentResources;
874     for (auto& statistic : m_resourceStatisticsMap.values()) {
875         if (statistic.isPrevalentResource && !hasHadUnexpiredRecentUserInteraction(statistic) && (!shouldCheckForGrandfathering || !statistic.grandfathered))
876             prevalentResources.append(statistic.highLevelDomain);
877
878         if (shouldClearGrandfathering && statistic.grandfathered)
879             statistic.grandfathered = false;
880     }
881
882     return prevalentResources;
883 }
884
885 void WebResourceLoadStatisticsStore::includeTodayAsOperatingDateIfNecessary()
886 {
887     ASSERT(!RunLoop::isMain());
888
889     auto today = OperatingDate::today();
890     if (!m_operatingDates.isEmpty() && today <= m_operatingDates.last())
891         return;
892
893     while (m_operatingDates.size() >= operatingDatesWindow)
894         m_operatingDates.remove(0);
895
896     m_operatingDates.append(today);
897 }
898
899 bool WebResourceLoadStatisticsStore::hasStatisticsExpired(const ResourceLoadStatistics& resourceStatistic) const
900 {
901     if (m_operatingDates.size() >= operatingDatesWindow) {
902         if (OperatingDate::fromWallTime(resourceStatistic.mostRecentUserInteractionTime) < m_operatingDates.first())
903             return true;
904     }
905
906     // If we don't meet the real criteria for an expired statistic, check the user setting for a tighter restriction (mainly for testing).
907     if (m_parameters.timeToLiveUserInteraction) {
908         if (WallTime::now() > resourceStatistic.mostRecentUserInteractionTime + m_parameters.timeToLiveUserInteraction.value())
909             return true;
910     }
911
912     return false;
913 }
914     
915 void WebResourceLoadStatisticsStore::setMaxStatisticsEntries(size_t maximumEntryCount)
916 {
917     m_parameters.maxStatisticsEntries = maximumEntryCount;
918 }
919     
920 void WebResourceLoadStatisticsStore::setPruneEntriesDownTo(size_t pruneTargetCount)
921 {
922     m_parameters.pruneEntriesDownTo = pruneTargetCount;
923 }
924     
925 struct StatisticsLastSeen {
926     String topPrivatelyOwnedDomain;
927     WallTime lastSeen;
928 };
929     
930 static void pruneResources(HashMap<String, WebCore::ResourceLoadStatistics>& statisticsMap, Vector<StatisticsLastSeen>& statisticsToPrune, size_t& numberOfEntriesToPrune)
931 {
932     if (statisticsToPrune.size() > numberOfEntriesToPrune) {
933         std::sort(statisticsToPrune.begin(), statisticsToPrune.end(), [](const StatisticsLastSeen& a, const StatisticsLastSeen& b) {
934             return a.lastSeen < b.lastSeen;
935         });
936     }
937
938     for (size_t i = 0, end = std::min(numberOfEntriesToPrune, statisticsToPrune.size()); i != end; ++i, --numberOfEntriesToPrune)
939         statisticsMap.remove(statisticsToPrune[i].topPrivatelyOwnedDomain);
940 }
941     
942 static unsigned computeImportance(const ResourceLoadStatistics& resourceStatistic)
943 {
944     unsigned importance = maxImportance;
945     if (!resourceStatistic.isPrevalentResource)
946         importance -= 1;
947     if (!resourceStatistic.hadUserInteraction)
948         importance -= 2;
949     return importance;
950 }
951     
952 void WebResourceLoadStatisticsStore::pruneStatisticsIfNeeded()
953 {
954     ASSERT(!RunLoop::isMain());
955     if (m_resourceStatisticsMap.size() <= m_parameters.maxStatisticsEntries)
956         return;
957
958     ASSERT(m_parameters.pruneEntriesDownTo <= m_parameters.maxStatisticsEntries);
959
960     size_t numberOfEntriesLeftToPrune = m_resourceStatisticsMap.size() - m_parameters.pruneEntriesDownTo;
961     ASSERT(numberOfEntriesLeftToPrune);
962     
963     Vector<StatisticsLastSeen> resourcesToPrunePerImportance[maxImportance + 1];
964     for (auto& resourceStatistic : m_resourceStatisticsMap.values())
965         resourcesToPrunePerImportance[computeImportance(resourceStatistic)].append({ resourceStatistic.highLevelDomain, resourceStatistic.lastSeen });
966     
967     for (unsigned importance = 0; numberOfEntriesLeftToPrune && importance <= maxImportance; ++importance)
968         pruneResources(m_resourceStatisticsMap, resourcesToPrunePerImportance[importance], numberOfEntriesLeftToPrune);
969
970     ASSERT(!numberOfEntriesLeftToPrune);
971 }
972
973 void WebResourceLoadStatisticsStore::resetParametersToDefaultValues()
974 {
975     m_parameters = { };
976 }
977
978 void WebResourceLoadStatisticsStore::logTestingEvent(const String& event)
979 {
980     if (!m_statisticsTestingCallback)
981         return;
982
983     if (RunLoop::isMain())
984         m_statisticsTestingCallback(event);
985     else {
986         RunLoop::main().dispatch([this, protectedThis = makeRef(*this), event = event.isolatedCopy()] {
987             if (m_statisticsTestingCallback)
988                 m_statisticsTestingCallback(event);
989         });
990     }
991 }
992
993 } // namespace WebKit