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