f5e6663c6360a7ecfdd289699d9dfbe50d170a7b
[WebKit-https.git] / Source / WebKit / NetworkProcess / Classifier / ResourceLoadStatisticsMemoryStore.cpp
1 /*
2  * Copyright (C) 2017-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 "ResourceLoadStatisticsMemoryStore.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/DocumentStorageAccess.h>
41 #include <WebCore/KeyedCoding.h>
42 #include <WebCore/NetworkStorageSession.h>
43 #include <WebCore/ResourceLoadStatistics.h>
44 #include <WebCore/UserGestureIndicator.h>
45 #include <wtf/CallbackAggregator.h>
46 #include <wtf/DateMath.h>
47 #include <wtf/MathExtras.h>
48 #include <wtf/text/StringBuilder.h>
49
50 namespace WebKit {
51 using namespace WebCore;
52
53 constexpr unsigned statisticsModelVersion { 16 };
54
55 struct StatisticsLastSeen {
56     RegistrableDomain domain;
57     WallTime lastSeen;
58 };
59
60 static void pruneResources(HashMap<RegistrableDomain, ResourceLoadStatistics>& statisticsMap, Vector<StatisticsLastSeen>& statisticsToPrune, size_t& numberOfEntriesToPrune)
61 {
62     if (statisticsToPrune.size() > numberOfEntriesToPrune) {
63         std::sort(statisticsToPrune.begin(), statisticsToPrune.end(), [](const StatisticsLastSeen& a, const StatisticsLastSeen& b) {
64             return a.lastSeen < b.lastSeen;
65         });
66     }
67
68     for (size_t i = 0, end = std::min(numberOfEntriesToPrune, statisticsToPrune.size()); i != end; ++i, --numberOfEntriesToPrune)
69         statisticsMap.remove(statisticsToPrune[i].domain);
70 }
71
72 ResourceLoadStatisticsMemoryStore::ResourceLoadStatisticsMemoryStore(WebResourceLoadStatisticsStore& store, WorkQueue& workQueue, ShouldIncludeLocalhost shouldIncludeLocalhost)
73     : ResourceLoadStatisticsStore(store, workQueue, shouldIncludeLocalhost)
74 {
75     RELEASE_ASSERT(!RunLoop::isMain());
76
77     workQueue.dispatchAfter(5_s, [weakThis = makeWeakPtr(*this)] {
78         if (weakThis)
79             weakThis->calculateAndSubmitTelemetry();
80     });
81 }
82
83 bool ResourceLoadStatisticsMemoryStore::isEmpty() const
84 {
85     RELEASE_ASSERT(!RunLoop::isMain());
86
87     return m_resourceStatisticsMap.isEmpty();
88 }
89
90 void ResourceLoadStatisticsMemoryStore::setPersistentStorage(ResourceLoadStatisticsPersistentStorage& persistentStorage)
91 {
92     ASSERT(!RunLoop::isMain());
93
94     m_persistentStorage = makeWeakPtr(persistentStorage);
95 }
96
97 void ResourceLoadStatisticsMemoryStore::calculateAndSubmitTelemetry() const
98 {
99     ASSERT(!RunLoop::isMain());
100
101     if (parameters().shouldSubmitTelemetry)
102         WebResourceLoadStatisticsTelemetry::calculateAndSubmit(*this);
103 }
104
105 void ResourceLoadStatisticsMemoryStore::incrementRecordsDeletedCountForDomains(HashSet<RegistrableDomain>&& domainsWithDeletedWebsiteData)
106 {
107     ASSERT(!RunLoop::isMain());
108
109     for (auto& domain : domainsWithDeletedWebsiteData) {
110         auto& statistic = ensureResourceStatisticsForRegistrableDomain(domain);
111         ++statistic.dataRecordsRemoved;
112     }
113 }
114
115 unsigned ResourceLoadStatisticsMemoryStore::recursivelyGetAllDomainsThatHaveRedirectedToThisDomain(const ResourceLoadStatistics& resourceStatistic, HashSet<RegistrableDomain>& domainsThatHaveRedirectedTo, unsigned numberOfRecursiveCalls) const
116 {
117     ASSERT(!RunLoop::isMain());
118
119     if (numberOfRecursiveCalls >= maxNumberOfRecursiveCallsInRedirectTraceBack) {
120         // Model version 14 invokes a deliberate re-classification of the whole set.
121         if (statisticsModelVersion != 14)
122             ASSERT_NOT_REACHED();
123         RELEASE_LOG(ResourceLoadStatistics, "Hit %u recursive calls in redirect backtrace. Returning early.", maxNumberOfRecursiveCallsInRedirectTraceBack);
124         return numberOfRecursiveCalls;
125     }
126
127     numberOfRecursiveCalls++;
128
129     for (auto& subresourceUniqueRedirectFromDomain : resourceStatistic.subresourceUniqueRedirectsFrom) {
130         auto mapEntry = m_resourceStatisticsMap.find(RegistrableDomain { subresourceUniqueRedirectFromDomain });
131         if (mapEntry == m_resourceStatisticsMap.end() || mapEntry->value.isPrevalentResource)
132             continue;
133         if (domainsThatHaveRedirectedTo.add(mapEntry->value.registrableDomain).isNewEntry)
134             numberOfRecursiveCalls = recursivelyGetAllDomainsThatHaveRedirectedToThisDomain(mapEntry->value, domainsThatHaveRedirectedTo, numberOfRecursiveCalls);
135     }
136     for (auto& topFrameUniqueRedirectFromDomain : resourceStatistic.topFrameUniqueRedirectsFrom) {
137         auto mapEntry = m_resourceStatisticsMap.find(RegistrableDomain { topFrameUniqueRedirectFromDomain });
138         if (mapEntry == m_resourceStatisticsMap.end() || mapEntry->value.isPrevalentResource)
139             continue;
140         if (domainsThatHaveRedirectedTo.add(mapEntry->value.registrableDomain).isNewEntry)
141             numberOfRecursiveCalls = recursivelyGetAllDomainsThatHaveRedirectedToThisDomain(mapEntry->value, domainsThatHaveRedirectedTo, numberOfRecursiveCalls);
142     }
143
144     return numberOfRecursiveCalls;
145 }
146
147 void ResourceLoadStatisticsMemoryStore::markAsPrevalentIfHasRedirectedToPrevalent(ResourceLoadStatistics& resourceStatistic)
148 {
149     ASSERT(!RunLoop::isMain());
150
151     if (resourceStatistic.isPrevalentResource)
152         return;
153
154     for (auto& subresourceDomainRedirectedTo : resourceStatistic.subresourceUniqueRedirectsTo) {
155         auto mapEntry = m_resourceStatisticsMap.find(RegistrableDomain { subresourceDomainRedirectedTo });
156         if (mapEntry != m_resourceStatisticsMap.end() && mapEntry->value.isPrevalentResource) {
157             setPrevalentResource(resourceStatistic, ResourceLoadPrevalence::High);
158             return;
159         }
160     }
161
162     for (auto& topFrameDomainRedirectedTo : resourceStatistic.topFrameUniqueRedirectsTo) {
163         auto mapEntry = m_resourceStatisticsMap.find(RegistrableDomain { topFrameDomainRedirectedTo });
164         if (mapEntry != m_resourceStatisticsMap.end() && mapEntry->value.isPrevalentResource) {
165             setPrevalentResource(resourceStatistic, ResourceLoadPrevalence::High);
166             return;
167         }
168     }
169 }
170
171 bool ResourceLoadStatisticsMemoryStore::isPrevalentDueToDebugMode(ResourceLoadStatistics& resourceStatistic)
172 {
173     if (!debugModeEnabled())
174         return false;
175
176     return resourceStatistic.registrableDomain == debugStaticPrevalentResource() || resourceStatistic.registrableDomain == debugManualPrevalentResource();
177 }
178
179 void ResourceLoadStatisticsMemoryStore::classifyPrevalentResources()
180 {
181     ASSERT(!RunLoop::isMain());
182
183     for (auto& resourceStatistic : m_resourceStatisticsMap.values()) {
184         if (shouldSkip(resourceStatistic.registrableDomain))
185             continue;
186         if (isPrevalentDueToDebugMode(resourceStatistic))
187             setPrevalentResource(resourceStatistic, ResourceLoadPrevalence::High);
188         else if (!resourceStatistic.isVeryPrevalentResource) {
189             markAsPrevalentIfHasRedirectedToPrevalent(resourceStatistic);
190             auto currentPrevalence = resourceStatistic.isPrevalentResource ? ResourceLoadPrevalence::High : ResourceLoadPrevalence::Low;
191             auto newPrevalence = classifier().calculateResourcePrevalence(resourceStatistic, currentPrevalence);
192             if (newPrevalence != currentPrevalence)
193                 setPrevalentResource(resourceStatistic, newPrevalence);
194         }
195     }
196 }
197
198 void ResourceLoadStatisticsMemoryStore::syncStorageIfNeeded()
199 {
200     ASSERT(!RunLoop::isMain());
201
202     if (m_persistentStorage)
203         m_persistentStorage->scheduleOrWriteMemoryStore(ResourceLoadStatisticsPersistentStorage::ForceImmediateWrite::No);
204 }
205
206 void ResourceLoadStatisticsMemoryStore::syncStorageImmediately()
207 {
208     ASSERT(!RunLoop::isMain());
209
210     if (m_persistentStorage)
211         m_persistentStorage->scheduleOrWriteMemoryStore(ResourceLoadStatisticsPersistentStorage::ForceImmediateWrite::Yes);
212 }
213
214 void ResourceLoadStatisticsMemoryStore::hasStorageAccess(const SubFrameDomain& subFrameDomain, const TopFrameDomain& topFrameDomain, Optional<FrameIdentifier> frameID, PageIdentifier pageID, CompletionHandler<void(bool)>&& completionHandler)
215 {
216     ASSERT(!RunLoop::isMain());
217
218     auto& subFrameStatistic = ensureResourceStatisticsForRegistrableDomain(subFrameDomain);
219     // Return false if this domain cannot ask for storage access.
220     if (shouldBlockAndPurgeCookies(subFrameStatistic)) {
221         completionHandler(false);
222         return;
223     }
224
225     if (!shouldBlockAndKeepCookies(subFrameStatistic)) {
226         RunLoop::main().dispatch([store = makeRef(store()), subFrameDomain = subFrameDomain.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
227             store->hasCookies(subFrameDomain, [store = store.copyRef(), completionHandler = WTFMove(completionHandler)](bool result) mutable {
228                 store->statisticsQueue().dispatch([completionHandler = WTFMove(completionHandler), result] () mutable {
229                     completionHandler(result);
230                 });
231             });
232         });
233         return;
234     }
235
236     RunLoop::main().dispatch([store = makeRef(store()), subFrameDomain = subFrameDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy(), frameID, pageID, completionHandler = WTFMove(completionHandler)]() mutable {
237         store->callHasStorageAccessForFrameHandler(subFrameDomain, topFrameDomain, frameID.value(), pageID, [store = store.copyRef(), completionHandler = WTFMove(completionHandler)](bool result) mutable {
238             store->statisticsQueue().dispatch([completionHandler = WTFMove(completionHandler), result] () mutable {
239                 completionHandler(result);
240             });
241         });
242     });
243 }
244
245 void ResourceLoadStatisticsMemoryStore::requestStorageAccess(SubFrameDomain&& subFrameDomain, TopFrameDomain&& topFrameDomain, FrameIdentifier frameID, PageIdentifier pageID, CompletionHandler<void(StorageAccessStatus)>&& completionHandler)
246 {
247     ASSERT(!RunLoop::isMain());
248
249     auto& subFrameStatistic = ensureResourceStatisticsForRegistrableDomain(subFrameDomain);
250     if (shouldBlockAndPurgeCookies(subFrameStatistic)) {
251         RELEASE_LOG_INFO_IF(debugLoggingEnabled(), ITPDebug, "Cannot grant storage access to %{public}s since its cookies are blocked in third-party contexts and it has not received user interaction as first-party.", subFrameDomain.string().utf8().data());
252         completionHandler(StorageAccessStatus::CannotRequestAccess);
253         return;
254     }
255
256     if (!shouldBlockAndKeepCookies(subFrameStatistic)) {
257         RELEASE_LOG_INFO_IF(debugLoggingEnabled(), ITPDebug, "No need to grant storage access to %{public}s since its cookies are not blocked in third-party contexts.", subFrameDomain.string().utf8().data());
258         completionHandler(StorageAccessStatus::HasAccess);
259         return;
260     }
261
262     auto userWasPromptedEarlier = hasUserGrantedStorageAccessThroughPrompt(subFrameStatistic, topFrameDomain);
263     if (userWasPromptedEarlier == StorageAccessPromptWasShown::No) {
264         RELEASE_LOG_INFO_IF(debugLoggingEnabled(), ITPDebug, "About to ask the user whether they want to grant storage access to %{public}s under %{public}s or not.", subFrameDomain.string().utf8().data(), topFrameDomain.string().utf8().data());
265         completionHandler(StorageAccessStatus::RequiresUserPrompt);
266         return;
267     }
268
269     if (userWasPromptedEarlier == StorageAccessPromptWasShown::Yes)
270         RELEASE_LOG_INFO_IF(debugLoggingEnabled(), ITPDebug, "Storage access was granted to %{public}s under %{public}s.", subFrameDomain.string().utf8().data(), topFrameDomain.string().utf8().data());
271
272     subFrameStatistic.timesAccessedAsFirstPartyDueToStorageAccessAPI++;
273
274     grantStorageAccessInternal(WTFMove(subFrameDomain), WTFMove(topFrameDomain), frameID, pageID, userWasPromptedEarlier, [completionHandler = WTFMove(completionHandler)] (StorageAccessWasGranted wasGranted) mutable {
275         completionHandler(wasGranted == StorageAccessWasGranted::Yes ? StorageAccessStatus::HasAccess : StorageAccessStatus::CannotRequestAccess);
276     });
277 }
278
279 void ResourceLoadStatisticsMemoryStore::requestStorageAccessUnderOpener(DomainInNeedOfStorageAccess&& domainInNeedOfStorageAccess, PageIdentifier openerPageID, OpenerDomain&& openerDomain)
280 {
281     ASSERT(domainInNeedOfStorageAccess != openerDomain);
282     ASSERT(!RunLoop::isMain());
283
284     if (domainInNeedOfStorageAccess == openerDomain)
285         return;
286
287     auto& domainInNeedOfStorageAccessStatistic = ensureResourceStatisticsForRegistrableDomain(domainInNeedOfStorageAccess);
288     auto cookiesBlockedAndPurged = shouldBlockAndPurgeCookies(domainInNeedOfStorageAccessStatistic);
289
290     // The domain already has access if its cookies are not blocked.
291     if (!cookiesBlockedAndPurged && !shouldBlockAndKeepCookies(domainInNeedOfStorageAccessStatistic))
292         return;
293
294     RELEASE_LOG_INFO_IF(debugLoggingEnabled(), ITPDebug, "[Temporary combatibility fix] Storage access was granted for %{public}s under opener page from %{public}s, with user interaction in the opened window.", domainInNeedOfStorageAccess.string().utf8().data(), openerDomain.string().utf8().data());
295     grantStorageAccessInternal(WTFMove(domainInNeedOfStorageAccess), WTFMove(openerDomain), WTF::nullopt, openerPageID, StorageAccessPromptWasShown::No, [](StorageAccessWasGranted) { });
296 }
297
298 void ResourceLoadStatisticsMemoryStore::grantStorageAccess(SubFrameDomain&& subFrameDomain, TopFrameDomain&& topFrameDomain, FrameIdentifier frameID, PageIdentifier pageID, StorageAccessPromptWasShown promptWasShown, CompletionHandler<void(StorageAccessWasGranted)>&& completionHandler)
299 {
300     ASSERT(!RunLoop::isMain());
301
302     if (promptWasShown == StorageAccessPromptWasShown::Yes) {
303         auto& subFrameStatistic = ensureResourceStatisticsForRegistrableDomain(subFrameDomain);
304         ASSERT(subFrameStatistic.hadUserInteraction);
305         subFrameStatistic.storageAccessUnderTopFrameDomains.add(topFrameDomain);
306     }
307     grantStorageAccessInternal(WTFMove(subFrameDomain), WTFMove(topFrameDomain), frameID, pageID, promptWasShown, WTFMove(completionHandler));
308 }
309
310 void ResourceLoadStatisticsMemoryStore::grantStorageAccessInternal(SubFrameDomain&& subFrameDomain, TopFrameDomain&& topFrameDomain, Optional<FrameIdentifier> frameID, PageIdentifier pageID, StorageAccessPromptWasShown promptWasShownNowOrEarlier, CompletionHandler<void(StorageAccessWasGranted)>&& completionHandler)
311 {
312     ASSERT(!RunLoop::isMain());
313
314     if (subFrameDomain == topFrameDomain) {
315         completionHandler(StorageAccessWasGranted::Yes);
316         return;
317     }
318
319     if (promptWasShownNowOrEarlier == StorageAccessPromptWasShown::Yes) {
320         auto& subFrameStatistic = ensureResourceStatisticsForRegistrableDomain(subFrameDomain);
321         ASSERT(subFrameStatistic.hadUserInteraction);
322         ASSERT(subFrameStatistic.storageAccessUnderTopFrameDomains.contains(topFrameDomain));
323         subFrameStatistic.mostRecentUserInteractionTime = WallTime::now();
324     }
325
326     RunLoop::main().dispatch([subFrameDomain = subFrameDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy(), frameID, pageID, store = makeRef(store()), completionHandler = WTFMove(completionHandler)]() mutable {
327         store->callGrantStorageAccessHandler(subFrameDomain, topFrameDomain, frameID, pageID, [completionHandler = WTFMove(completionHandler), store = store.copyRef()](StorageAccessWasGranted wasGranted) mutable {
328             store->statisticsQueue().dispatch([wasGranted, completionHandler = WTFMove(completionHandler)] () mutable {
329                 completionHandler(wasGranted);
330             });
331         });
332     });
333 }
334
335 void ResourceLoadStatisticsMemoryStore::grandfatherDataForDomains(const HashSet<RegistrableDomain>& domains)
336 {
337     ASSERT(!RunLoop::isMain());
338
339     for (auto& domain : domains) {
340         auto& statistic = ensureResourceStatisticsForRegistrableDomain(domain);
341         statistic.grandfathered = true;
342     }
343 }
344
345 Vector<RegistrableDomain> ResourceLoadStatisticsMemoryStore::ensurePrevalentResourcesForDebugMode()
346 {
347     ASSERT(!RunLoop::isMain());
348
349     if (!debugModeEnabled())
350         return { };
351
352     Vector<RegistrableDomain> domainsToBlock;
353     domainsToBlock.reserveInitialCapacity(2);
354
355     auto& staticSesourceStatistic = ensureResourceStatisticsForRegistrableDomain(debugStaticPrevalentResource());
356     setPrevalentResource(staticSesourceStatistic, ResourceLoadPrevalence::High);
357     domainsToBlock.uncheckedAppend(debugStaticPrevalentResource());
358
359     if (!debugManualPrevalentResource().isEmpty()) {
360         auto& manualResourceStatistic = ensureResourceStatisticsForRegistrableDomain(debugManualPrevalentResource());
361         setPrevalentResource(manualResourceStatistic, ResourceLoadPrevalence::High);
362         domainsToBlock.uncheckedAppend(debugManualPrevalentResource());
363         RELEASE_LOG_INFO(ITPDebug, "Did set %{public}s as prevalent resource for the purposes of ITP Debug Mode.", debugManualPrevalentResource().string().utf8().data());
364     }
365     
366     return domainsToBlock;
367 }
368
369 void ResourceLoadStatisticsMemoryStore::logFrameNavigation(const RegistrableDomain& targetDomain, const RegistrableDomain& topFrameDomain, const RegistrableDomain& sourceDomain, bool isRedirect, bool isMainFrame)
370 {
371     ASSERT(!RunLoop::isMain());
372
373     bool areTargetAndTopFrameDomainsSameSite = targetDomain == topFrameDomain;
374     bool areTargetAndSourceDomainsSameSite = targetDomain == sourceDomain;
375
376     bool statisticsWereUpdated = false;
377     if (!isMainFrame && !(areTargetAndTopFrameDomainsSameSite || areTargetAndSourceDomainsSameSite)) {
378         auto& targetStatistics = ensureResourceStatisticsForRegistrableDomain(targetDomain);
379         targetStatistics.lastSeen = ResourceLoadStatistics::reduceTimeResolution(WallTime::now());
380         if (targetStatistics.subframeUnderTopFrameDomains.add(topFrameDomain).isNewEntry)
381             statisticsWereUpdated = true;
382     }
383
384     if (isRedirect && !areTargetAndSourceDomainsSameSite) {
385         if (isMainFrame) {
386             auto& redirectingDomainStatistics = ensureResourceStatisticsForRegistrableDomain(sourceDomain);
387             if (redirectingDomainStatistics.topFrameUniqueRedirectsTo.add(targetDomain).isNewEntry)
388                 statisticsWereUpdated = true;
389             auto& targetStatistics = ensureResourceStatisticsForRegistrableDomain(targetDomain);
390             if (targetStatistics.topFrameUniqueRedirectsFrom.add(sourceDomain).isNewEntry)
391                 statisticsWereUpdated = true;
392         } else {
393             auto& redirectingDomainStatistics = ensureResourceStatisticsForRegistrableDomain(sourceDomain);
394             if (redirectingDomainStatistics.subresourceUniqueRedirectsTo.add(targetDomain).isNewEntry)
395                 statisticsWereUpdated = true;
396             auto& targetStatistics = ensureResourceStatisticsForRegistrableDomain(targetDomain);
397             if (targetStatistics.subresourceUniqueRedirectsFrom.add(sourceDomain).isNewEntry)
398                 statisticsWereUpdated = true;
399         }
400     }
401
402     if (statisticsWereUpdated)
403         scheduleStatisticsProcessingRequestIfNecessary();
404 }
405
406 void ResourceLoadStatisticsMemoryStore::logUserInteraction(const TopFrameDomain& domain)
407 {
408     ASSERT(!RunLoop::isMain());
409
410     auto& statistics = ensureResourceStatisticsForRegistrableDomain(domain);
411     statistics.hadUserInteraction = true;
412     statistics.mostRecentUserInteractionTime = WallTime::now();
413 }
414
415 void ResourceLoadStatisticsMemoryStore::logCrossSiteLoadWithLinkDecoration(const NavigatedFromDomain& fromDomain, const NavigatedToDomain& toDomain)
416 {
417     ASSERT(!RunLoop::isMain());
418     ASSERT(fromDomain != toDomain);
419
420     auto& toStatistics = ensureResourceStatisticsForRegistrableDomain(toDomain);
421     toStatistics.topFrameLinkDecorationsFrom.add(fromDomain);
422     
423     auto& fromStatistics = ensureResourceStatisticsForRegistrableDomain(fromDomain);
424     if (fromStatistics.isPrevalentResource)
425         toStatistics.gotLinkDecorationFromPrevalentResource = true;
426 }
427
428 void ResourceLoadStatisticsMemoryStore::clearUserInteraction(const RegistrableDomain& domain)
429 {
430     ASSERT(!RunLoop::isMain());
431
432     auto& statistics = ensureResourceStatisticsForRegistrableDomain(domain);
433     statistics.hadUserInteraction = false;
434     statistics.mostRecentUserInteractionTime = { };
435 }
436
437 bool ResourceLoadStatisticsMemoryStore::hasHadUserInteraction(const RegistrableDomain& domain, OperatingDatesWindow operatingDatesWindow)
438 {
439     ASSERT(!RunLoop::isMain());
440
441     auto mapEntry = m_resourceStatisticsMap.find(domain);
442     return mapEntry == m_resourceStatisticsMap.end() ? false: hasHadUnexpiredRecentUserInteraction(mapEntry->value, operatingDatesWindow);
443 }
444
445 void ResourceLoadStatisticsMemoryStore::setPrevalentResource(ResourceLoadStatistics& resourceStatistic, ResourceLoadPrevalence newPrevalence)
446 {
447     ASSERT(!RunLoop::isMain());
448
449     if (shouldSkip(resourceStatistic.registrableDomain))
450         return;
451
452     resourceStatistic.isPrevalentResource = true;
453     resourceStatistic.isVeryPrevalentResource = newPrevalence == ResourceLoadPrevalence::VeryHigh;
454     HashSet<RegistrableDomain> domainsThatHaveRedirectedTo;
455     recursivelyGetAllDomainsThatHaveRedirectedToThisDomain(resourceStatistic, domainsThatHaveRedirectedTo, 0);
456     for (auto& domain : domainsThatHaveRedirectedTo) {
457         auto mapEntry = m_resourceStatisticsMap.find(domain);
458         if (mapEntry == m_resourceStatisticsMap.end())
459             continue;
460         ASSERT(!mapEntry->value.isPrevalentResource);
461         mapEntry->value.isPrevalentResource = true;
462     }
463 }
464     
465 void ResourceLoadStatisticsMemoryStore::dumpResourceLoadStatistics(CompletionHandler<void(const String&)>&& completionHandler)
466 {
467     ASSERT(!RunLoop::isMain());
468     if (dataRecordsBeingRemoved()) {
469         m_dataRecordRemovalCompletionHandlers.append([this, completionHandler = WTFMove(completionHandler)]() mutable {
470             dumpResourceLoadStatistics(WTFMove(completionHandler));
471         });
472         return;
473     }
474
475     StringBuilder result;
476     result.appendLiteral("Resource load statistics:\n\n");
477     for (auto& mapEntry : m_resourceStatisticsMap.values())
478         result.append(mapEntry.toString());
479     completionHandler(result.toString());
480 }
481
482 bool ResourceLoadStatisticsMemoryStore::isPrevalentResource(const RegistrableDomain& domain) const
483 {
484     ASSERT(!RunLoop::isMain());
485
486     if (shouldSkip(domain))
487         return false;
488
489     auto mapEntry = m_resourceStatisticsMap.find(domain);
490     return mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.isPrevalentResource;
491 }
492
493 bool ResourceLoadStatisticsMemoryStore::isVeryPrevalentResource(const RegistrableDomain& domain) const
494 {
495     ASSERT(!RunLoop::isMain());
496
497     if (shouldSkip(domain))
498         return false;
499     
500     auto mapEntry = m_resourceStatisticsMap.find(domain);
501     return mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.isPrevalentResource && mapEntry->value.isVeryPrevalentResource;
502 }
503
504 bool ResourceLoadStatisticsMemoryStore::isRegisteredAsSubresourceUnder(const SubResourceDomain& subresourceDomain, const TopFrameDomain& topFrameDomain) const
505 {
506     ASSERT(!RunLoop::isMain());
507
508     auto mapEntry = m_resourceStatisticsMap.find(subresourceDomain);
509     return mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.subresourceUnderTopFrameDomains.contains(topFrameDomain);
510 }
511
512 bool ResourceLoadStatisticsMemoryStore::isRegisteredAsSubFrameUnder(const SubFrameDomain& subFrameDomain, const TopFrameDomain& topFrameDomain) const
513 {
514     ASSERT(!RunLoop::isMain());
515
516     auto mapEntry = m_resourceStatisticsMap.find(subFrameDomain);
517     return mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.subframeUnderTopFrameDomains.contains(topFrameDomain);
518 }
519
520 bool ResourceLoadStatisticsMemoryStore::isRegisteredAsRedirectingTo(const RedirectedFromDomain& redirectedFromDomain, const RedirectedToDomain& redirectedToDomain) const
521 {
522     ASSERT(!RunLoop::isMain());
523
524     auto mapEntry = m_resourceStatisticsMap.find(redirectedFromDomain);
525     return mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.subresourceUniqueRedirectsTo.contains(redirectedToDomain);
526 }
527
528 void ResourceLoadStatisticsMemoryStore::clearPrevalentResource(const RegistrableDomain& domain)
529 {
530     ASSERT(!RunLoop::isMain());
531
532     auto& statistics = ensureResourceStatisticsForRegistrableDomain(domain);
533     statistics.isPrevalentResource = false;
534     statistics.isVeryPrevalentResource = false;
535 }
536
537 void ResourceLoadStatisticsMemoryStore::setGrandfathered(const RegistrableDomain& domain, bool value)
538 {
539     ASSERT(!RunLoop::isMain());
540
541     auto& statistics = ensureResourceStatisticsForRegistrableDomain(domain);
542     statistics.grandfathered = value;
543 }
544
545 bool ResourceLoadStatisticsMemoryStore::isGrandfathered(const RegistrableDomain& domain) const
546 {
547     ASSERT(!RunLoop::isMain());
548
549     auto mapEntry = m_resourceStatisticsMap.find(domain);
550     return mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.grandfathered;
551 }
552
553 void ResourceLoadStatisticsMemoryStore::setSubframeUnderTopFrameDomain(const SubFrameDomain& subFrameDomain, const TopFrameDomain& topFrameDomain)
554 {
555     ASSERT(!RunLoop::isMain());
556
557     auto& statistics = ensureResourceStatisticsForRegistrableDomain(subFrameDomain);
558     statistics.subframeUnderTopFrameDomains.add(topFrameDomain);
559     // For consistency, make sure we also have a statistics entry for the top frame domain.
560     ensureResourceStatisticsForRegistrableDomain(topFrameDomain);
561 }
562
563 void ResourceLoadStatisticsMemoryStore::setSubresourceUnderTopFrameDomain(const SubResourceDomain& subresourceDomain, const RegistrableDomain& topFrameDomain)
564 {
565     ASSERT(!RunLoop::isMain());
566
567     auto& statistics = ensureResourceStatisticsForRegistrableDomain(subresourceDomain);
568     statistics.subresourceUnderTopFrameDomains.add(topFrameDomain);
569     // For consistency, make sure we also have a statistics entry for the top frame domain.
570     ensureResourceStatisticsForRegistrableDomain(topFrameDomain);
571 }
572
573 void ResourceLoadStatisticsMemoryStore::setSubresourceUniqueRedirectTo(const SubResourceDomain& subresourceDomain, const RedirectDomain& redirectDomain)
574 {
575     ASSERT(!RunLoop::isMain());
576
577     auto& statistics = ensureResourceStatisticsForRegistrableDomain(subresourceDomain);
578     statistics.subresourceUniqueRedirectsTo.add(redirectDomain);
579     // For consistency, make sure we also have a statistics entry for the redirect domain.
580     ensureResourceStatisticsForRegistrableDomain(redirectDomain);
581 }
582
583 void ResourceLoadStatisticsMemoryStore::setSubresourceUniqueRedirectFrom(const SubResourceDomain& subresourceDomain, const RedirectDomain& redirectDomain)
584 {
585     ASSERT(!RunLoop::isMain());
586
587     auto& statistics = ensureResourceStatisticsForRegistrableDomain(subresourceDomain);
588     statistics.subresourceUniqueRedirectsFrom.add(redirectDomain);
589     // For consistency, make sure we also have a statistics entry for the redirect domain.
590     ensureResourceStatisticsForRegistrableDomain(redirectDomain);
591 }
592
593 void ResourceLoadStatisticsMemoryStore::setTopFrameUniqueRedirectTo(const TopFrameDomain& topFrameDomain, const RedirectDomain& redirectDomain)
594 {
595     ASSERT(!RunLoop::isMain());
596
597     auto& statistics = ensureResourceStatisticsForRegistrableDomain(topFrameDomain);
598     statistics.topFrameUniqueRedirectsTo.add(redirectDomain);
599     // For consistency, make sure we also have a statistics entry for the redirect domain.
600     ensureResourceStatisticsForRegistrableDomain(redirectDomain);
601 }
602
603 void ResourceLoadStatisticsMemoryStore::setTopFrameUniqueRedirectFrom(const TopFrameDomain& topFrameDomain, const RedirectDomain& redirectDomain)
604 {
605     ASSERT(!RunLoop::isMain());
606
607     auto& statistics = ensureResourceStatisticsForRegistrableDomain(topFrameDomain);
608     statistics.topFrameUniqueRedirectsFrom.add(redirectDomain);
609     // For consistency, make sure we also have a statistics entry for the redirect domain.
610     ensureResourceStatisticsForRegistrableDomain(redirectDomain);
611 }
612
613 ResourceLoadStatistics& ResourceLoadStatisticsMemoryStore::ensureResourceStatisticsForRegistrableDomain(const RegistrableDomain& domain)
614 {
615     ASSERT(!RunLoop::isMain());
616
617     return m_resourceStatisticsMap.ensure(domain, [&domain] {
618         return ResourceLoadStatistics(domain);
619     }).iterator->value;
620 }
621
622 std::unique_ptr<KeyedEncoder> ResourceLoadStatisticsMemoryStore::createEncoderFromData() const
623 {
624     ASSERT(!RunLoop::isMain());
625
626     auto encoder = KeyedEncoder::encoder();
627     encoder->encodeUInt32("version", statisticsModelVersion);
628     encoder->encodeDouble("endOfGrandfatheringTimestamp", endOfGrandfatheringTimestamp().secondsSinceEpoch().value());
629
630     encoder->encodeObjects("browsingStatistics", m_resourceStatisticsMap.begin(), m_resourceStatisticsMap.end(), [](KeyedEncoder& encoderInner, const auto& domain) {
631         domain.value.encode(encoderInner);
632     });
633
634     auto& operatingDates = this->operatingDates();
635     encoder->encodeObjects("operatingDates", operatingDates.begin(), operatingDates.end(), [](KeyedEncoder& encoderInner, OperatingDate date) {
636         encoderInner.encodeDouble("date", date.secondsSinceEpoch().value());
637     });
638
639     return encoder;
640 }
641
642 void ResourceLoadStatisticsMemoryStore::mergeWithDataFromDecoder(KeyedDecoder& decoder)
643 {
644     ASSERT(!RunLoop::isMain());
645
646     unsigned versionOnDisk;
647     if (!decoder.decodeUInt32("version", versionOnDisk))
648         return;
649
650     if (versionOnDisk > statisticsModelVersion) {
651         WTFLogAlways("Found resource load statistics on disk with model version %u whereas the highest supported version is %u. Resetting.", versionOnDisk, statisticsModelVersion);
652         return;
653     }
654
655     double endOfGrandfatheringTimestamp;
656     if (decoder.decodeDouble("endOfGrandfatheringTimestamp", endOfGrandfatheringTimestamp))
657         setEndOfGrandfatheringTimestamp(WallTime::fromRawSeconds(endOfGrandfatheringTimestamp));
658     else
659         clearEndOfGrandfatheringTimeStamp();
660
661     Vector<ResourceLoadStatistics> loadedStatistics;
662     bool succeeded = decoder.decodeObjects("browsingStatistics", loadedStatistics, [versionOnDisk](KeyedDecoder& decoderInner, ResourceLoadStatistics& statistics) {
663         return statistics.decode(decoderInner, versionOnDisk);
664     });
665
666     if (!succeeded)
667         return;
668
669     mergeStatistics(WTFMove(loadedStatistics));
670     updateCookieBlocking([]() { });
671
672     Vector<OperatingDate> operatingDates;
673     succeeded = decoder.decodeObjects("operatingDates", operatingDates, [](KeyedDecoder& decoder, OperatingDate& date) {
674         double value;
675         if (!decoder.decodeDouble("date", value))
676             return false;
677
678         date = OperatingDate::fromWallTime(WallTime::fromRawSeconds(value));
679         return true;
680     });
681
682     if (!succeeded)
683         return;
684
685     mergeOperatingDates(WTFMove(operatingDates));
686 }
687
688 void ResourceLoadStatisticsMemoryStore::clear(CompletionHandler<void()>&& completionHandler)
689 {
690     ASSERT(!RunLoop::isMain());
691
692     m_resourceStatisticsMap.clear();
693     clearOperatingDates();
694
695     auto callbackAggregator = CallbackAggregator::create(WTFMove(completionHandler));
696
697     removeAllStorageAccess([callbackAggregator = callbackAggregator.copyRef()] { });
698
699     auto registrableDomainsToBlockAndDeleteCookiesFor = ensurePrevalentResourcesForDebugMode();
700     RegistrableDomainsToBlockCookiesFor domainsToBlock { registrableDomainsToBlockAndDeleteCookiesFor, { } };
701     updateCookieBlockingForDomains(domainsToBlock, [callbackAggregator = callbackAggregator.copyRef()] { });
702 }
703
704 bool ResourceLoadStatisticsMemoryStore::wasAccessedAsFirstPartyDueToUserInteraction(const ResourceLoadStatistics& current, const ResourceLoadStatistics& updated) const
705 {
706     if (!current.hadUserInteraction && !updated.hadUserInteraction)
707         return false;
708
709     auto mostRecentUserInteractionTime = std::max(current.mostRecentUserInteractionTime, updated.mostRecentUserInteractionTime);
710
711     return updated.lastSeen <= mostRecentUserInteractionTime + 24_h;
712 }
713
714 void ResourceLoadStatisticsMemoryStore::mergeStatistics(Vector<ResourceLoadStatistics>&& statistics)
715 {
716     ASSERT(!RunLoop::isMain());
717
718     for (auto& statistic : statistics) {
719         auto result = m_resourceStatisticsMap.ensure(statistic.registrableDomain, [&statistic] {
720             return WTFMove(statistic);
721         });
722         if (!result.isNewEntry) {
723             if (wasAccessedAsFirstPartyDueToUserInteraction(result.iterator->value, statistic))
724                 result.iterator->value.timesAccessedAsFirstPartyDueToUserInteraction++;
725             result.iterator->value.merge(statistic);
726         }
727     }
728 }
729
730 bool ResourceLoadStatisticsMemoryStore::shouldBlockAndKeepCookies(const ResourceLoadStatistics& statistic)
731 {
732     return statistic.isPrevalentResource && statistic.hadUserInteraction;
733 }
734
735 bool ResourceLoadStatisticsMemoryStore::shouldBlockAndPurgeCookies(const ResourceLoadStatistics& statistic)
736 {
737     return statistic.isPrevalentResource && !statistic.hadUserInteraction;
738 }
739
740 StorageAccessPromptWasShown ResourceLoadStatisticsMemoryStore::hasUserGrantedStorageAccessThroughPrompt(const ResourceLoadStatistics& statistic, const RegistrableDomain& firstPartyDomain)
741 {
742     return statistic.storageAccessUnderTopFrameDomains.contains(firstPartyDomain) ? StorageAccessPromptWasShown::Yes : StorageAccessPromptWasShown::No;
743 }
744
745 void ResourceLoadStatisticsMemoryStore::updateCookieBlocking(CompletionHandler<void()>&& completionHandler)
746 {
747     ASSERT(!RunLoop::isMain());
748
749     Vector<RegistrableDomain> domainsToBlockAndDeleteCookiesFor;
750     Vector<RegistrableDomain> domainsToBlockButKeepCookiesFor;
751     for (auto& resourceStatistic : m_resourceStatisticsMap.values()) {
752         if (resourceStatistic.isPrevalentResource) {
753             if (hasHadUnexpiredRecentUserInteraction(resourceStatistic, OperatingDatesWindow::Long))
754                 domainsToBlockButKeepCookiesFor.append(resourceStatistic.registrableDomain);
755             else
756                 domainsToBlockAndDeleteCookiesFor.append(resourceStatistic.registrableDomain);
757         }
758     }
759
760     if (domainsToBlockAndDeleteCookiesFor.isEmpty() && domainsToBlockButKeepCookiesFor.isEmpty() && !debugModeEnabled()) {
761         completionHandler();
762         return;
763     }
764
765     RegistrableDomainsToBlockCookiesFor domainsToBlock { domainsToBlockAndDeleteCookiesFor, domainsToBlockButKeepCookiesFor };
766
767     if (debugLoggingEnabled() && !domainsToBlockAndDeleteCookiesFor.isEmpty() && !domainsToBlockButKeepCookiesFor.isEmpty())
768         debugLogDomainsInBatches("block", domainsToBlock);
769
770     RunLoop::main().dispatch([weakThis = makeWeakPtr(*this), store = makeRef(store()), domainsToBlock = crossThreadCopy(domainsToBlock), completionHandler = WTFMove(completionHandler)] () mutable {
771         store->callUpdatePrevalentDomainsToBlockCookiesForHandler(domainsToBlock, [weakThis = WTFMove(weakThis), store = store.copyRef(), completionHandler = WTFMove(completionHandler)]() mutable {
772             store->statisticsQueue().dispatch([weakThis = WTFMove(weakThis), completionHandler = WTFMove(completionHandler)]() mutable {
773                 completionHandler();
774                 if (!weakThis)
775                     return;
776                 RELEASE_LOG_INFO_IF(weakThis->debugLoggingEnabled(), ITPDebug, "Done updating cookie blocking.");
777             });
778         });
779     });
780 }
781
782 void ResourceLoadStatisticsMemoryStore::processStatistics(const Function<void(const ResourceLoadStatistics&)>& processFunction) const
783 {
784     ASSERT(!RunLoop::isMain());
785
786     for (auto& resourceStatistic : m_resourceStatisticsMap.values())
787         processFunction(resourceStatistic);
788 }
789
790 bool ResourceLoadStatisticsMemoryStore::hasHadUnexpiredRecentUserInteraction(ResourceLoadStatistics& resourceStatistic, OperatingDatesWindow operatingDatesWindow) const
791 {
792     ASSERT(!RunLoop::isMain());
793
794     if (resourceStatistic.hadUserInteraction && hasStatisticsExpired(resourceStatistic, operatingDatesWindow)) {
795         // Drop privacy sensitive data because we no longer need it.
796         // Set timestamp to 0 so that statistics merge will know
797         // it has been reset as opposed to its default -1.
798         resourceStatistic.mostRecentUserInteractionTime = { };
799         resourceStatistic.storageAccessUnderTopFrameDomains.clear();
800         resourceStatistic.hadUserInteraction = false;
801     }
802
803     return resourceStatistic.hadUserInteraction;
804 }
805
806 bool ResourceLoadStatisticsMemoryStore::shouldRemoveAllWebsiteDataFor(ResourceLoadStatistics& resourceStatistic, bool shouldCheckForGrandfathering) const
807 {
808     return resourceStatistic.isPrevalentResource && !hasHadUnexpiredRecentUserInteraction(resourceStatistic, OperatingDatesWindow::Long) && (!shouldCheckForGrandfathering || !resourceStatistic.grandfathered);
809 }
810
811 bool ResourceLoadStatisticsMemoryStore::shouldRemoveAllButCookiesFor(ResourceLoadStatistics& resourceStatistic, bool shouldCheckForGrandfathering) const
812 {
813     return resourceStatistic.gotLinkDecorationFromPrevalentResource && !hasHadUnexpiredRecentUserInteraction(resourceStatistic, OperatingDatesWindow::Short) && (!shouldCheckForGrandfathering || !resourceStatistic.grandfathered);
814 }
815
816 Vector<std::pair<RegistrableDomain, WebsiteDataToRemove>> ResourceLoadStatisticsMemoryStore::registrableDomainsToRemoveWebsiteDataFor()
817 {
818     ASSERT(!RunLoop::isMain());
819
820     bool shouldCheckForGrandfathering = endOfGrandfatheringTimestamp() > WallTime::now();
821     bool shouldClearGrandfathering = !shouldCheckForGrandfathering && endOfGrandfatheringTimestamp();
822
823     if (shouldClearGrandfathering)
824         clearEndOfGrandfatheringTimeStamp();
825
826     Vector<std::pair<RegistrableDomain, WebsiteDataToRemove>> domainsToRemoveWebsiteDataFor;
827     for (auto& statistic : m_resourceStatisticsMap.values()) {
828         if (shouldRemoveAllWebsiteDataFor(statistic, shouldCheckForGrandfathering))
829             domainsToRemoveWebsiteDataFor.append(std::make_pair(statistic.registrableDomain, WebsiteDataToRemove::All));
830         else if (shouldRemoveAllButCookiesFor(statistic, shouldCheckForGrandfathering)) {
831             domainsToRemoveWebsiteDataFor.append(std::make_pair(statistic.registrableDomain, WebsiteDataToRemove::AllButCookies));
832             statistic.gotLinkDecorationFromPrevalentResource = false;
833         }
834
835         if (shouldClearGrandfathering && statistic.grandfathered)
836             statistic.grandfathered = false;
837     }
838
839     return domainsToRemoveWebsiteDataFor;
840 }
841
842 void ResourceLoadStatisticsMemoryStore::pruneStatisticsIfNeeded()
843 {
844     ASSERT(!RunLoop::isMain());
845
846     if (m_resourceStatisticsMap.size() <= parameters().maxStatisticsEntries)
847         return;
848
849     ASSERT(parameters().pruneEntriesDownTo <= parameters().maxStatisticsEntries);
850
851     size_t numberOfEntriesLeftToPrune = m_resourceStatisticsMap.size() - parameters().pruneEntriesDownTo;
852     ASSERT(numberOfEntriesLeftToPrune);
853
854     Vector<StatisticsLastSeen> resourcesToPrunePerImportance[maxImportance + 1];
855     for (auto& resourceStatistic : m_resourceStatisticsMap.values())
856         resourcesToPrunePerImportance[computeImportance(resourceStatistic)].append({ resourceStatistic.registrableDomain, resourceStatistic.lastSeen });
857
858     for (unsigned importance = 0; numberOfEntriesLeftToPrune && importance <= maxImportance; ++importance)
859         pruneResources(m_resourceStatisticsMap, resourcesToPrunePerImportance[importance], numberOfEntriesLeftToPrune);
860
861     ASSERT(!numberOfEntriesLeftToPrune);
862 }
863
864 void ResourceLoadStatisticsMemoryStore::setLastSeen(const RegistrableDomain& domain, Seconds seconds)
865 {
866     ASSERT(!RunLoop::isMain());
867
868     auto& statistics = ensureResourceStatisticsForRegistrableDomain(domain);
869     statistics.lastSeen = WallTime::fromRawSeconds(seconds.seconds());
870 }
871
872 void ResourceLoadStatisticsMemoryStore::setPrevalentResource(const RegistrableDomain& domain)
873 {
874     ASSERT(!RunLoop::isMain());
875
876     if (shouldSkip(domain))
877         return;
878     
879     auto& resourceStatistic = ensureResourceStatisticsForRegistrableDomain(domain);
880     setPrevalentResource(resourceStatistic, ResourceLoadPrevalence::High);
881 }
882
883 void ResourceLoadStatisticsMemoryStore::setVeryPrevalentResource(const RegistrableDomain& domain)
884 {
885     ASSERT(!RunLoop::isMain());
886
887     if (shouldSkip(domain))
888         return;
889     
890     auto& resourceStatistic = ensureResourceStatisticsForRegistrableDomain(domain);
891     setPrevalentResource(resourceStatistic, ResourceLoadPrevalence::VeryHigh);
892 }
893
894 } // namespace WebKit
895
896 #endif