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