2 * Copyright (C) 2016-2018 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "WebResourceLoadStatisticsStore.h"
30 #include "PluginProcessManager.h"
31 #include "PluginProcessProxy.h"
32 #include "WebProcessMessages.h"
33 #include "WebProcessProxy.h"
34 #include "WebResourceLoadStatisticsStoreMessages.h"
35 #include "WebResourceLoadStatisticsTelemetry.h"
36 #include "WebsiteDataFetchOption.h"
37 #include "WebsiteDataStore.h"
38 #include <WebCore/KeyedCoding.h>
39 #include <WebCore/ResourceLoadStatistics.h>
40 #include <wtf/CrossThreadCopier.h>
41 #include <wtf/DateMath.h>
42 #include <wtf/MathExtras.h>
43 #include <wtf/NeverDestroyed.h>
45 using namespace WebCore;
49 constexpr unsigned operatingDatesWindow { 30 };
50 constexpr unsigned statisticsModelVersion { 10 };
51 constexpr unsigned maxImportance { 3 };
53 template<typename T> static inline String isolatedPrimaryDomain(const T& value)
55 return ResourceLoadStatistics::primaryDomain(value).isolatedCopy();
58 const OptionSet<WebsiteDataType>& WebResourceLoadStatisticsStore::monitoredDataTypes()
60 static NeverDestroyed<OptionSet<WebsiteDataType>> dataTypes(std::initializer_list<WebsiteDataType>({
61 WebsiteDataType::Cookies,
62 WebsiteDataType::DOMCache,
63 WebsiteDataType::IndexedDBDatabases,
64 WebsiteDataType::LocalStorage,
65 WebsiteDataType::MediaKeys,
66 WebsiteDataType::OfflineWebApplicationCache,
67 #if ENABLE(NETSCAPE_PLUGIN_API)
68 WebsiteDataType::PlugInData,
70 WebsiteDataType::SearchFieldRecentSearches,
71 WebsiteDataType::SessionStorage,
72 #if ENABLE(SERVICE_WORKER)
73 WebsiteDataType::ServiceWorkerRegistrations,
75 WebsiteDataType::WebSQLDatabases,
78 ASSERT(RunLoop::isMain());
85 OperatingDate() = default;
87 static OperatingDate fromWallTime(WallTime time)
89 double ms = time.secondsSinceEpoch().milliseconds();
90 int year = msToYear(ms);
91 int yearDay = dayInYear(ms, year);
92 int month = monthFromDayInYear(yearDay, isLeapYear(year));
93 int monthDay = dayInMonthFromDayInYear(yearDay, isLeapYear(year));
95 return OperatingDate { year, month, monthDay };
98 static OperatingDate today()
100 return OperatingDate::fromWallTime(WallTime::now());
103 Seconds secondsSinceEpoch() const
105 return Seconds { dateToDaysFrom1970(m_year, m_month, m_monthDay) * secondsPerDay };
108 bool operator==(const OperatingDate& other) const
110 return m_monthDay == other.m_monthDay && m_month == other.m_month && m_year == other.m_year;
113 bool operator<(const OperatingDate& other) const
115 return secondsSinceEpoch() < other.secondsSinceEpoch();
118 bool operator<=(const OperatingDate& other) const
120 return secondsSinceEpoch() <= other.secondsSinceEpoch();
124 OperatingDate(int year, int month, int monthDay)
127 , m_monthDay(monthDay)
131 int m_month { 0 }; // [0, 11].
132 int m_monthDay { 0 }; // [1, 31].
135 static Vector<OperatingDate> mergeOperatingDates(const Vector<OperatingDate>& existingDates, Vector<OperatingDate>&& newDates)
137 if (existingDates.isEmpty())
138 return WTFMove(newDates);
140 Vector<OperatingDate> mergedDates(existingDates.size() + newDates.size());
142 // Merge the two sorted vectors of dates.
143 std::merge(existingDates.begin(), existingDates.end(), newDates.begin(), newDates.end(), mergedDates.begin());
144 // Remove duplicate dates.
145 removeRepeatedElements(mergedDates);
147 // Drop old dates until the Vector size reaches operatingDatesWindow.
148 while (mergedDates.size() > operatingDatesWindow)
149 mergedDates.remove(0);
154 WebResourceLoadStatisticsStore::WebResourceLoadStatisticsStore(const String& resourceLoadStatisticsDirectory, Function<void(const String&)>&& testingCallback, bool isEphemeral, UpdatePrevalentDomainsToPartitionOrBlockCookiesHandler&& updatePrevalentDomainsToPartitionOrBlockCookiesHandler, HasStorageAccessForFrameHandler&& hasStorageAccessForFrameHandler, GrantStorageAccessForFrameHandler&& grantStorageAccessForFrameHandler, RemovePrevalentDomainsHandler&& removeDomainsHandler)
155 : m_statisticsQueue(WorkQueue::create("WebResourceLoadStatisticsStore Process Data Queue", WorkQueue::Type::Serial, WorkQueue::QOS::Utility))
156 , m_persistentStorage(*this, resourceLoadStatisticsDirectory, isEphemeral ? ResourceLoadStatisticsPersistentStorage::IsReadOnly::Yes : ResourceLoadStatisticsPersistentStorage::IsReadOnly::No)
157 , m_updatePrevalentDomainsToPartitionOrBlockCookiesHandler(WTFMove(updatePrevalentDomainsToPartitionOrBlockCookiesHandler))
158 , m_hasStorageAccessForFrameHandler(WTFMove(hasStorageAccessForFrameHandler))
159 , m_grantStorageAccessForFrameHandler(WTFMove(grantStorageAccessForFrameHandler))
160 , m_removeDomainsHandler(WTFMove(removeDomainsHandler))
161 , m_dailyTasksTimer(RunLoop::main(), this, &WebResourceLoadStatisticsStore::performDailyTasks)
162 , m_statisticsTestingCallback(WTFMove(testingCallback))
164 ASSERT(RunLoop::isMain());
167 registerUserDefaultsIfNeeded();
170 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
171 m_persistentStorage.initialize();
172 includeTodayAsOperatingDateIfNecessary();
175 m_statisticsQueue->dispatchAfter(5_s, [this, protectedThis = makeRef(*this)] {
176 if (m_parameters.shouldSubmitTelemetry)
177 WebResourceLoadStatisticsTelemetry::calculateAndSubmit(*this);
180 m_dailyTasksTimer.startRepeating(24_h);
183 WebResourceLoadStatisticsStore::~WebResourceLoadStatisticsStore()
185 m_persistentStorage.finishAllPendingWorkSynchronously();
188 void WebResourceLoadStatisticsStore::removeDataRecords()
190 ASSERT(!RunLoop::isMain());
192 if (!shouldRemoveDataRecords())
195 #if ENABLE(NETSCAPE_PLUGIN_API)
196 m_activePluginTokens.clear();
197 for (auto plugin : PluginProcessManager::singleton().pluginProcesses())
198 m_activePluginTokens.add(plugin->pluginProcessToken());
201 auto prevalentResourceDomains = topPrivatelyControlledDomainsToRemoveWebsiteDataFor();
202 if (prevalentResourceDomains.isEmpty())
205 setDataRecordsBeingRemoved(true);
207 RunLoop::main().dispatch([prevalentResourceDomains = crossThreadCopy(prevalentResourceDomains), this, protectedThis = makeRef(*this)] () mutable {
208 WebProcessProxy::deleteWebsiteDataForTopPrivatelyControlledDomainsInAllPersistentDataStores(WebResourceLoadStatisticsStore::monitoredDataTypes(), WTFMove(prevalentResourceDomains), m_parameters.shouldNotifyPagesWhenDataRecordsWereScanned, [this, protectedThis = WTFMove(protectedThis)](const HashSet<String>& domainsWithDeletedWebsiteData) mutable {
209 m_statisticsQueue->dispatch([this, protectedThis = WTFMove(protectedThis), topDomains = crossThreadCopy(domainsWithDeletedWebsiteData)] () mutable {
210 for (auto& prevalentResourceDomain : topDomains) {
211 auto& statistic = ensureResourceStatisticsForPrimaryDomain(prevalentResourceDomain);
212 ++statistic.dataRecordsRemoved;
214 setDataRecordsBeingRemoved(false);
220 void WebResourceLoadStatisticsStore::scheduleStatisticsAndDataRecordsProcessing()
222 ASSERT(RunLoop::isMain());
223 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
224 processStatisticsAndDataRecords();
228 void WebResourceLoadStatisticsStore::processStatisticsAndDataRecords()
230 ASSERT(!RunLoop::isMain());
232 if (m_parameters.shouldClassifyResourcesBeforeDataRecordsRemoval) {
233 for (auto& resourceStatistic : m_resourceStatisticsMap.values()) {
234 if (!resourceStatistic.isPrevalentResource && m_resourceLoadStatisticsClassifier.hasPrevalentResourceCharacteristics(resourceStatistic))
235 resourceStatistic.isPrevalentResource = true;
240 pruneStatisticsIfNeeded();
242 if (m_parameters.shouldNotifyPagesWhenDataRecordsWereScanned) {
243 RunLoop::main().dispatch([] {
244 WebProcessProxy::notifyPageStatisticsAndDataRecordsProcessed();
248 m_persistentStorage.scheduleOrWriteMemoryStore(ResourceLoadStatisticsPersistentStorage::ForceImmediateWrite::No);
251 void WebResourceLoadStatisticsStore::resourceLoadStatisticsUpdated(Vector<WebCore::ResourceLoadStatistics>&& origins)
253 ASSERT(!RunLoop::isMain());
255 mergeStatistics(WTFMove(origins));
256 // Fire before processing statistics to propagate user interaction as fast as possible to the network process.
257 updateCookiePartitioning();
258 processStatisticsAndDataRecords();
261 void WebResourceLoadStatisticsStore::hasStorageAccess(String&& subFrameHost, String&& topFrameHost, uint64_t frameID, uint64_t pageID, WTF::CompletionHandler<void (bool)>&& callback)
263 ASSERT(subFrameHost != topFrameHost);
264 ASSERT(RunLoop::isMain());
266 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), subFramePrimaryDomain = isolatedPrimaryDomain(subFrameHost), topFramePrimaryDomain = isolatedPrimaryDomain(topFrameHost), frameID, pageID, callback = WTFMove(callback)] () mutable {
268 auto& subFrameStatistic = ensureResourceStatisticsForPrimaryDomain(subFramePrimaryDomain);
269 if (shouldBlockCookies(subFrameStatistic)) {
274 if (!shouldPartitionCookies(subFrameStatistic)) {
279 m_hasStorageAccessForFrameHandler(subFramePrimaryDomain, topFramePrimaryDomain, frameID, pageID, WTFMove(callback));
283 void WebResourceLoadStatisticsStore::requestStorageAccess(String&& subFrameHost, String&& topFrameHost, uint64_t frameID, uint64_t pageID, WTF::CompletionHandler<void (bool)>&& callback)
285 ASSERT(subFrameHost != topFrameHost);
286 ASSERT(RunLoop::isMain());
288 auto subFramePrimaryDomain = isolatedPrimaryDomain(subFrameHost);
289 auto topFramePrimaryDomain = isolatedPrimaryDomain(topFrameHost);
290 if (subFramePrimaryDomain == topFramePrimaryDomain) {
295 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), subFramePrimaryDomain = crossThreadCopy(subFramePrimaryDomain), topFramePrimaryDomain = crossThreadCopy(topFramePrimaryDomain), frameID, pageID, callback = WTFMove(callback)] () mutable {
297 auto& subFrameStatistic = ensureResourceStatisticsForPrimaryDomain(subFramePrimaryDomain);
298 if (shouldBlockCookies(subFrameStatistic)) {
303 if (!shouldPartitionCookies(subFrameStatistic)) {
308 subFrameStatistic.timesAccessedAsFirstPartyDueToStorageAccessAPI++;
310 m_grantStorageAccessForFrameHandler(subFramePrimaryDomain, topFramePrimaryDomain, frameID, pageID, WTFMove(callback));
314 void WebResourceLoadStatisticsStore::grandfatherExistingWebsiteData(CompletionHandler<void()>&& callback)
316 ASSERT(!RunLoop::isMain());
318 RunLoop::main().dispatch([this, protectedThis = makeRef(*this), callback = WTFMove(callback)] () mutable {
319 // FIXME: This method being a static call on WebProcessProxy is wrong.
320 // It should be on the data store that this object belongs to.
321 WebProcessProxy::topPrivatelyControlledDomainsWithWebsiteData(WebResourceLoadStatisticsStore::monitoredDataTypes(), m_parameters.shouldNotifyPagesWhenDataRecordsWereScanned, [this, protectedThis = WTFMove(protectedThis), callback = WTFMove(callback)] (HashSet<String>&& topPrivatelyControlledDomainsWithWebsiteData) mutable {
322 m_statisticsQueue->dispatch([this, protectedThis = WTFMove(protectedThis), topDomains = crossThreadCopy(topPrivatelyControlledDomainsWithWebsiteData), callback = WTFMove(callback)] () mutable {
323 for (auto& topPrivatelyControlledDomain : topDomains) {
324 auto& statistic = ensureResourceStatisticsForPrimaryDomain(topPrivatelyControlledDomain);
325 statistic.grandfathered = true;
327 m_endOfGrandfatheringTimestamp = WallTime::now() + m_parameters.grandfatheringTime;
328 m_persistentStorage.scheduleOrWriteMemoryStore(ResourceLoadStatisticsPersistentStorage::ForceImmediateWrite::Yes);
330 logTestingEvent(ASCIILiteral("Grandfathered"));
336 void WebResourceLoadStatisticsStore::processWillOpenConnection(WebProcessProxy&, IPC::Connection& connection)
338 connection.addWorkQueueMessageReceiver(Messages::WebResourceLoadStatisticsStore::messageReceiverName(), m_statisticsQueue.get(), this);
341 void WebResourceLoadStatisticsStore::processDidCloseConnection(WebProcessProxy&, IPC::Connection& connection)
343 connection.removeWorkQueueMessageReceiver(Messages::WebResourceLoadStatisticsStore::messageReceiverName());
346 void WebResourceLoadStatisticsStore::applicationWillTerminate()
348 m_persistentStorage.finishAllPendingWorkSynchronously();
351 void WebResourceLoadStatisticsStore::performDailyTasks()
353 ASSERT(RunLoop::isMain());
355 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
356 includeTodayAsOperatingDateIfNecessary();
358 if (m_parameters.shouldSubmitTelemetry)
362 void WebResourceLoadStatisticsStore::submitTelemetry()
364 ASSERT(RunLoop::isMain());
365 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
366 WebResourceLoadStatisticsTelemetry::calculateAndSubmit(*this);
370 void WebResourceLoadStatisticsStore::setResourceLoadStatisticsDebugMode(bool enable)
373 setTimeToLiveCookiePartitionFree(30_s);
375 resetParametersToDefaultValues();
378 void WebResourceLoadStatisticsStore::logUserInteraction(const URL& url)
380 if (url.isBlankURL() || url.isEmpty())
383 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url)] {
384 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
385 statistics.hadUserInteraction = true;
386 statistics.mostRecentUserInteractionTime = WallTime::now();
388 if (statistics.isMarkedForCookiePartitioning || statistics.isMarkedForCookieBlocking)
389 updateCookiePartitioningForDomains({ }, { }, { primaryDomain }, ShouldClearFirst::No);
393 void WebResourceLoadStatisticsStore::logNonRecentUserInteraction(const URL& url)
395 if (url.isBlankURL() || url.isEmpty())
398 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url)] {
399 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
400 statistics.hadUserInteraction = true;
401 statistics.mostRecentUserInteractionTime = WallTime::now() - (m_parameters.timeToLiveCookiePartitionFree + Seconds::fromHours(1));
403 updateCookiePartitioningForDomains({ primaryDomain }, { }, { }, ShouldClearFirst::No);
407 void WebResourceLoadStatisticsStore::clearUserInteraction(const URL& url)
409 if (url.isBlankURL() || url.isEmpty())
412 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url)] {
413 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
414 statistics.hadUserInteraction = false;
415 statistics.mostRecentUserInteractionTime = { };
419 void WebResourceLoadStatisticsStore::hasHadUserInteraction(const URL& url, WTF::Function<void (bool)>&& completionHandler)
421 if (url.isBlankURL() || url.isEmpty()) {
422 completionHandler(false);
426 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url), completionHandler = WTFMove(completionHandler)] () mutable {
427 auto mapEntry = m_resourceStatisticsMap.find(primaryDomain);
428 bool hadUserInteraction = mapEntry == m_resourceStatisticsMap.end() ? false: hasHadUnexpiredRecentUserInteraction(mapEntry->value);
429 RunLoop::main().dispatch([hadUserInteraction, completionHandler = WTFMove(completionHandler)] {
430 completionHandler(hadUserInteraction);
435 void WebResourceLoadStatisticsStore::setLastSeen(const URL& url, Seconds seconds)
437 if (url.isBlankURL() || url.isEmpty())
440 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url), seconds] {
441 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
442 statistics.lastSeen = WallTime::fromRawSeconds(seconds.seconds());
446 void WebResourceLoadStatisticsStore::setPrevalentResource(const URL& url)
448 if (url.isBlankURL() || url.isEmpty())
451 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url)] {
452 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
453 statistics.isPrevalentResource = true;
457 void WebResourceLoadStatisticsStore::isPrevalentResource(const URL& url, WTF::Function<void (bool)>&& completionHandler)
459 if (url.isBlankURL() || url.isEmpty()) {
460 completionHandler(false);
464 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url), completionHandler = WTFMove(completionHandler)] () mutable {
465 auto mapEntry = m_resourceStatisticsMap.find(primaryDomain);
466 bool isPrevalentResource = mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.isPrevalentResource;
467 RunLoop::main().dispatch([isPrevalentResource, completionHandler = WTFMove(completionHandler)] {
468 completionHandler(isPrevalentResource);
473 void WebResourceLoadStatisticsStore::isRegisteredAsSubFrameUnder(const URL& subFrame, const URL& topFrame, WTF::Function<void (bool)>&& completionHandler)
475 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), subFramePrimaryDomain = isolatedPrimaryDomain(subFrame), topFramePrimaryDomain = isolatedPrimaryDomain(topFrame), completionHandler = WTFMove(completionHandler)] () mutable {
476 auto mapEntry = m_resourceStatisticsMap.find(subFramePrimaryDomain);
477 bool isRegisteredAsSubFrameUnder = mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.subframeUnderTopFrameOrigins.contains(topFramePrimaryDomain);
478 RunLoop::main().dispatch([isRegisteredAsSubFrameUnder, completionHandler = WTFMove(completionHandler)] {
479 completionHandler(isRegisteredAsSubFrameUnder);
484 void WebResourceLoadStatisticsStore::isRegisteredAsRedirectingTo(const URL& hostRedirectedFrom, const URL& hostRedirectedTo, WTF::Function<void (bool)>&& completionHandler)
486 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), hostRedirectedFromPrimaryDomain = isolatedPrimaryDomain(hostRedirectedFrom), hostRedirectedToPrimaryDomain = isolatedPrimaryDomain(hostRedirectedTo), completionHandler = WTFMove(completionHandler)] () mutable {
487 auto mapEntry = m_resourceStatisticsMap.find(hostRedirectedFromPrimaryDomain);
488 bool isRegisteredAsRedirectingTo = mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.subresourceUniqueRedirectsTo.contains(hostRedirectedToPrimaryDomain);
489 RunLoop::main().dispatch([isRegisteredAsRedirectingTo, completionHandler = WTFMove(completionHandler)] {
490 completionHandler(isRegisteredAsRedirectingTo);
495 void WebResourceLoadStatisticsStore::clearPrevalentResource(const URL& url)
497 if (url.isBlankURL() || url.isEmpty())
500 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url)] {
501 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
502 statistics.isPrevalentResource = false;
506 void WebResourceLoadStatisticsStore::setGrandfathered(const URL& url, bool value)
508 if (url.isBlankURL() || url.isEmpty())
511 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url), value] {
512 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
513 statistics.grandfathered = value;
517 void WebResourceLoadStatisticsStore::isGrandfathered(const URL& url, WTF::Function<void (bool)>&& completionHandler)
519 if (url.isBlankURL() || url.isEmpty()) {
520 completionHandler(false);
524 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler), primaryDomain = isolatedPrimaryDomain(url)] () mutable {
525 auto mapEntry = m_resourceStatisticsMap.find(primaryDomain);
526 bool isGrandFathered = mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.grandfathered;
527 RunLoop::main().dispatch([isGrandFathered, completionHandler = WTFMove(completionHandler)] {
528 completionHandler(isGrandFathered);
533 void WebResourceLoadStatisticsStore::setSubframeUnderTopFrameOrigin(const URL& subframe, const URL& topFrame)
535 if (subframe.isBlankURL() || subframe.isEmpty() || topFrame.isBlankURL() || topFrame.isEmpty())
538 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryTopFrameDomain = isolatedPrimaryDomain(topFrame), primarySubFrameDomain = isolatedPrimaryDomain(subframe)] {
539 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primarySubFrameDomain);
540 statistics.subframeUnderTopFrameOrigins.add(primaryTopFrameDomain);
544 void WebResourceLoadStatisticsStore::setSubresourceUnderTopFrameOrigin(const URL& subresource, const URL& topFrame)
546 if (subresource.isBlankURL() || subresource.isEmpty() || topFrame.isBlankURL() || topFrame.isEmpty())
549 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryTopFrameDomain = isolatedPrimaryDomain(topFrame), primarySubresourceDomain = isolatedPrimaryDomain(subresource)] {
550 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primarySubresourceDomain);
551 statistics.subresourceUnderTopFrameOrigins.add(primaryTopFrameDomain);
555 void WebResourceLoadStatisticsStore::setSubresourceUniqueRedirectTo(const URL& subresource, const URL& hostNameRedirectedTo)
557 if (subresource.isBlankURL() || subresource.isEmpty() || hostNameRedirectedTo.isBlankURL() || hostNameRedirectedTo.isEmpty())
560 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryRedirectDomain = isolatedPrimaryDomain(hostNameRedirectedTo), primarySubresourceDomain = isolatedPrimaryDomain(subresource)] {
561 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primarySubresourceDomain);
562 statistics.subresourceUniqueRedirectsTo.add(primaryRedirectDomain);
566 void WebResourceLoadStatisticsStore::scheduleCookiePartitioningUpdate()
568 // Helper function used by testing system. Should only be called from the main thread.
569 ASSERT(RunLoop::isMain());
571 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
572 updateCookiePartitioning();
576 void WebResourceLoadStatisticsStore::scheduleCookiePartitioningUpdateForDomains(const Vector<String>& domainsToPartition, const Vector<String>& domainsToBlock, const Vector<String>& domainsToNeitherPartitionNorBlock, ShouldClearFirst shouldClearFirst)
578 // Helper function used by testing system. Should only be called from the main thread.
579 ASSERT(RunLoop::isMain());
580 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), domainsToPartition = crossThreadCopy(domainsToPartition), domainsToBlock = crossThreadCopy(domainsToBlock), domainsToNeitherPartitionNorBlock = crossThreadCopy(domainsToNeitherPartitionNorBlock), shouldClearFirst] {
581 updateCookiePartitioningForDomains(domainsToPartition, domainsToBlock, domainsToNeitherPartitionNorBlock, shouldClearFirst);
585 void WebResourceLoadStatisticsStore::scheduleClearPartitioningStateForDomains(const Vector<String>& domains)
587 // Helper function used by testing system. Should only be called from the main thread.
588 ASSERT(RunLoop::isMain());
589 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), domains = crossThreadCopy(domains)] {
590 clearPartitioningStateForDomains(domains);
594 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
595 void WebResourceLoadStatisticsStore::scheduleCookiePartitioningStateReset()
597 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
598 resetCookiePartitioningState();
603 void WebResourceLoadStatisticsStore::scheduleClearInMemory()
605 ASSERT(RunLoop::isMain());
606 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
611 void WebResourceLoadStatisticsStore::scheduleClearInMemoryAndPersistent(ShouldGrandfather shouldGrandfather, CompletionHandler<void()>&& callback)
613 ASSERT(RunLoop::isMain());
614 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), shouldGrandfather, callback = WTFMove(callback)] () mutable {
616 m_persistentStorage.clear();
618 if (shouldGrandfather == ShouldGrandfather::Yes)
619 grandfatherExistingWebsiteData([protectedThis = makeRef(*this), callback = WTFMove(callback)]() {
628 void WebResourceLoadStatisticsStore::scheduleClearInMemoryAndPersistent(WallTime modifiedSince, ShouldGrandfather shouldGrandfather, CompletionHandler<void()>&& callback)
630 // For now, be conservative and clear everything regardless of modifiedSince.
631 UNUSED_PARAM(modifiedSince);
632 scheduleClearInMemoryAndPersistent(shouldGrandfather, WTFMove(callback));
635 void WebResourceLoadStatisticsStore::setTimeToLiveUserInteraction(Seconds seconds)
637 ASSERT(seconds >= 0_s);
638 m_parameters.timeToLiveUserInteraction = seconds;
641 void WebResourceLoadStatisticsStore::setTimeToLiveCookiePartitionFree(Seconds seconds)
643 ASSERT(seconds >= 0_s);
644 m_parameters.timeToLiveCookiePartitionFree = seconds;
647 void WebResourceLoadStatisticsStore::setMinimumTimeBetweenDataRecordsRemoval(Seconds seconds)
649 ASSERT(seconds >= 0_s);
650 m_parameters.minimumTimeBetweenDataRecordsRemoval = seconds;
653 void WebResourceLoadStatisticsStore::setGrandfatheringTime(Seconds seconds)
655 ASSERT(seconds >= 0_s);
656 m_parameters.grandfatheringTime = seconds;
659 bool WebResourceLoadStatisticsStore::shouldRemoveDataRecords() const
661 ASSERT(!RunLoop::isMain());
662 if (m_dataRecordsBeingRemoved)
665 #if ENABLE(NETSCAPE_PLUGIN_API)
666 for (auto plugin : PluginProcessManager::singleton().pluginProcesses()) {
667 if (!m_activePluginTokens.contains(plugin->pluginProcessToken()))
672 return !m_lastTimeDataRecordsWereRemoved || MonotonicTime::now() >= (m_lastTimeDataRecordsWereRemoved + m_parameters.minimumTimeBetweenDataRecordsRemoval);
675 void WebResourceLoadStatisticsStore::setDataRecordsBeingRemoved(bool value)
677 ASSERT(!RunLoop::isMain());
678 m_dataRecordsBeingRemoved = value;
679 if (m_dataRecordsBeingRemoved)
680 m_lastTimeDataRecordsWereRemoved = MonotonicTime::now();
683 ResourceLoadStatistics& WebResourceLoadStatisticsStore::ensureResourceStatisticsForPrimaryDomain(const String& primaryDomain)
685 ASSERT(!RunLoop::isMain());
686 return m_resourceStatisticsMap.ensure(primaryDomain, [&primaryDomain] {
687 return ResourceLoadStatistics(primaryDomain);
691 std::unique_ptr<KeyedEncoder> WebResourceLoadStatisticsStore::createEncoderFromData() const
693 ASSERT(!RunLoop::isMain());
694 auto encoder = KeyedEncoder::encoder();
695 encoder->encodeUInt32("version", statisticsModelVersion);
696 encoder->encodeDouble("endOfGrandfatheringTimestamp", m_endOfGrandfatheringTimestamp.secondsSinceEpoch().value());
698 encoder->encodeObjects("browsingStatistics", m_resourceStatisticsMap.begin(), m_resourceStatisticsMap.end(), [](KeyedEncoder& encoderInner, const auto& origin) {
699 origin.value.encode(encoderInner);
702 encoder->encodeObjects("operatingDates", m_operatingDates.begin(), m_operatingDates.end(), [](KeyedEncoder& encoderInner, OperatingDate date) {
703 encoderInner.encodeDouble("date", date.secondsSinceEpoch().value());
709 void WebResourceLoadStatisticsStore::mergeWithDataFromDecoder(KeyedDecoder& decoder)
711 ASSERT(!RunLoop::isMain());
713 unsigned versionOnDisk;
714 if (!decoder.decodeUInt32("version", versionOnDisk))
717 if (versionOnDisk != statisticsModelVersion)
720 double endOfGrandfatheringTimestamp;
721 if (decoder.decodeDouble("endOfGrandfatheringTimestamp", endOfGrandfatheringTimestamp))
722 m_endOfGrandfatheringTimestamp = WallTime::fromRawSeconds(endOfGrandfatheringTimestamp);
724 m_endOfGrandfatheringTimestamp = { };
726 Vector<ResourceLoadStatistics> loadedStatistics;
727 bool succeeded = decoder.decodeObjects("browsingStatistics", loadedStatistics, [](KeyedDecoder& decoderInner, ResourceLoadStatistics& statistics) {
728 return statistics.decode(decoderInner);
734 mergeStatistics(WTFMove(loadedStatistics));
735 updateCookiePartitioning();
737 Vector<OperatingDate> operatingDates;
738 succeeded = decoder.decodeObjects("operatingDates", operatingDates, [](KeyedDecoder& decoder, OperatingDate& date) {
740 if (!decoder.decodeDouble("date", value))
743 date = OperatingDate::fromWallTime(WallTime::fromRawSeconds(value));
750 m_operatingDates = mergeOperatingDates(m_operatingDates, WTFMove(operatingDates));
753 void WebResourceLoadStatisticsStore::clearInMemory()
755 ASSERT(!RunLoop::isMain());
756 m_resourceStatisticsMap.clear();
757 m_operatingDates.clear();
759 updateCookiePartitioningForDomains({ }, { }, { }, ShouldClearFirst::Yes);
762 bool WebResourceLoadStatisticsStore::wasAccessedAsFirstPartyDueToUserInteraction(const ResourceLoadStatistics& current, const ResourceLoadStatistics& updated)
764 if (!current.hadUserInteraction && !updated.hadUserInteraction)
767 auto mostRecentUserInteractionTime = std::max(current.mostRecentUserInteractionTime, updated.mostRecentUserInteractionTime);
769 return updated.lastSeen <= mostRecentUserInteractionTime + m_parameters.timeToLiveCookiePartitionFree;
772 void WebResourceLoadStatisticsStore::mergeStatistics(Vector<ResourceLoadStatistics>&& statistics)
774 ASSERT(!RunLoop::isMain());
775 for (auto& statistic : statistics) {
776 auto result = m_resourceStatisticsMap.ensure(statistic.highLevelDomain, [&statistic] {
777 return WTFMove(statistic);
779 if (!result.isNewEntry) {
780 if (wasAccessedAsFirstPartyDueToUserInteraction(result.iterator->value, statistic))
781 result.iterator->value.timesAccessedAsFirstPartyDueToUserInteraction++;
782 result.iterator->value.merge(statistic);
787 bool WebResourceLoadStatisticsStore::shouldPartitionCookies(const ResourceLoadStatistics& statistic) const
789 return statistic.isPrevalentResource && statistic.hadUserInteraction && WallTime::now() > statistic.mostRecentUserInteractionTime + m_parameters.timeToLiveCookiePartitionFree;
792 bool WebResourceLoadStatisticsStore::shouldBlockCookies(const ResourceLoadStatistics& statistic) const
794 return statistic.isPrevalentResource && !statistic.hadUserInteraction;
797 void WebResourceLoadStatisticsStore::updateCookiePartitioning()
799 ASSERT(!RunLoop::isMain());
801 Vector<String> domainsToPartition;
802 Vector<String> domainsToBlock;
803 Vector<String> domainsToNeitherPartitionNorBlock;
804 for (auto& resourceStatistic : m_resourceStatisticsMap.values()) {
806 bool shouldPartition = shouldPartitionCookies(resourceStatistic);
807 bool shouldBlock = shouldBlockCookies(resourceStatistic);
809 if (shouldPartition && !resourceStatistic.isMarkedForCookiePartitioning) {
810 domainsToPartition.append(resourceStatistic.highLevelDomain);
811 resourceStatistic.isMarkedForCookiePartitioning = true;
812 } else if (shouldBlock && !resourceStatistic.isMarkedForCookieBlocking) {
813 domainsToBlock.append(resourceStatistic.highLevelDomain);
814 resourceStatistic.isMarkedForCookieBlocking = true;
815 } else if (!shouldPartition && !shouldBlock && resourceStatistic.isPrevalentResource) {
816 domainsToNeitherPartitionNorBlock.append(resourceStatistic.highLevelDomain);
817 resourceStatistic.isMarkedForCookiePartitioning = false;
818 resourceStatistic.isMarkedForCookieBlocking = false;
822 if (domainsToPartition.isEmpty() && domainsToBlock.isEmpty() && domainsToNeitherPartitionNorBlock.isEmpty())
825 RunLoop::main().dispatch([this, protectedThis = makeRef(*this), domainsToPartition = crossThreadCopy(domainsToPartition), domainsToBlock = crossThreadCopy(domainsToBlock), domainsToNeitherPartitionNorBlock = crossThreadCopy(domainsToNeitherPartitionNorBlock)] () {
826 m_updatePrevalentDomainsToPartitionOrBlockCookiesHandler(domainsToPartition, domainsToBlock, domainsToNeitherPartitionNorBlock, ShouldClearFirst::No);
830 void WebResourceLoadStatisticsStore::updateCookiePartitioningForDomains(const Vector<String>& domainsToPartition, const Vector<String>& domainsToBlock, const Vector<String>& domainsToNeitherPartitionNorBlock, ShouldClearFirst shouldClearFirst)
832 ASSERT(!RunLoop::isMain());
833 if (domainsToPartition.isEmpty() && domainsToBlock.isEmpty() && domainsToNeitherPartitionNorBlock.isEmpty() && shouldClearFirst == ShouldClearFirst::No)
836 RunLoop::main().dispatch([this, shouldClearFirst, protectedThis = makeRef(*this), domainsToPartition = crossThreadCopy(domainsToPartition), domainsToBlock = crossThreadCopy(domainsToBlock), domainsToNeitherPartitionNorBlock = crossThreadCopy(domainsToNeitherPartitionNorBlock)] () {
837 m_updatePrevalentDomainsToPartitionOrBlockCookiesHandler(domainsToPartition, domainsToBlock, domainsToNeitherPartitionNorBlock, shouldClearFirst);
840 if (shouldClearFirst == ShouldClearFirst::Yes)
841 resetCookiePartitioningState();
843 for (auto& domain : domainsToNeitherPartitionNorBlock) {
844 auto& statistic = ensureResourceStatisticsForPrimaryDomain(domain);
845 statistic.isMarkedForCookiePartitioning = false;
846 statistic.isMarkedForCookieBlocking = false;
850 for (auto& domain : domainsToPartition)
851 ensureResourceStatisticsForPrimaryDomain(domain).isMarkedForCookiePartitioning = true;
853 for (auto& domain : domainsToBlock)
854 ensureResourceStatisticsForPrimaryDomain(domain).isMarkedForCookieBlocking = true;
857 void WebResourceLoadStatisticsStore::clearPartitioningStateForDomains(const Vector<String>& domains)
859 ASSERT(!RunLoop::isMain());
860 if (domains.isEmpty())
863 RunLoop::main().dispatch([this, protectedThis = makeRef(*this), domains = crossThreadCopy(domains)] () {
864 m_removeDomainsHandler(domains);
867 for (auto& domain : domains) {
868 auto& statistic = ensureResourceStatisticsForPrimaryDomain(domain);
869 statistic.isMarkedForCookiePartitioning = false;
870 statistic.isMarkedForCookieBlocking = false;
874 void WebResourceLoadStatisticsStore::resetCookiePartitioningState()
876 ASSERT(!RunLoop::isMain());
877 for (auto& resourceStatistic : m_resourceStatisticsMap.values()) {
878 resourceStatistic.isMarkedForCookiePartitioning = false;
879 resourceStatistic.isMarkedForCookieBlocking = false;
883 void WebResourceLoadStatisticsStore::processStatistics(const WTF::Function<void (const ResourceLoadStatistics&)>& processFunction) const
885 ASSERT(!RunLoop::isMain());
886 for (auto& resourceStatistic : m_resourceStatisticsMap.values())
887 processFunction(resourceStatistic);
890 bool WebResourceLoadStatisticsStore::hasHadUnexpiredRecentUserInteraction(ResourceLoadStatistics& resourceStatistic) const
892 if (resourceStatistic.hadUserInteraction && hasStatisticsExpired(resourceStatistic)) {
893 // Drop privacy sensitive data because we no longer need it.
894 // Set timestamp to 0 so that statistics merge will know
895 // it has been reset as opposed to its default -1.
896 resourceStatistic.mostRecentUserInteractionTime = { };
897 resourceStatistic.hadUserInteraction = false;
900 return resourceStatistic.hadUserInteraction;
903 Vector<String> WebResourceLoadStatisticsStore::topPrivatelyControlledDomainsToRemoveWebsiteDataFor()
905 ASSERT(!RunLoop::isMain());
907 bool shouldCheckForGrandfathering = m_endOfGrandfatheringTimestamp > WallTime::now();
908 bool shouldClearGrandfathering = !shouldCheckForGrandfathering && m_endOfGrandfatheringTimestamp;
910 if (shouldClearGrandfathering)
911 m_endOfGrandfatheringTimestamp = { };
913 Vector<String> prevalentResources;
914 for (auto& statistic : m_resourceStatisticsMap.values()) {
915 if (statistic.isPrevalentResource && !hasHadUnexpiredRecentUserInteraction(statistic) && (!shouldCheckForGrandfathering || !statistic.grandfathered))
916 prevalentResources.append(statistic.highLevelDomain);
918 if (shouldClearGrandfathering && statistic.grandfathered)
919 statistic.grandfathered = false;
922 return prevalentResources;
925 void WebResourceLoadStatisticsStore::includeTodayAsOperatingDateIfNecessary()
927 ASSERT(!RunLoop::isMain());
929 auto today = OperatingDate::today();
930 if (!m_operatingDates.isEmpty() && today <= m_operatingDates.last())
933 while (m_operatingDates.size() >= operatingDatesWindow)
934 m_operatingDates.remove(0);
936 m_operatingDates.append(today);
939 bool WebResourceLoadStatisticsStore::hasStatisticsExpired(const ResourceLoadStatistics& resourceStatistic) const
941 if (m_operatingDates.size() >= operatingDatesWindow) {
942 if (OperatingDate::fromWallTime(resourceStatistic.mostRecentUserInteractionTime) < m_operatingDates.first())
946 // If we don't meet the real criteria for an expired statistic, check the user setting for a tighter restriction (mainly for testing).
947 if (m_parameters.timeToLiveUserInteraction) {
948 if (WallTime::now() > resourceStatistic.mostRecentUserInteractionTime + m_parameters.timeToLiveUserInteraction.value())
955 void WebResourceLoadStatisticsStore::setMaxStatisticsEntries(size_t maximumEntryCount)
957 m_parameters.maxStatisticsEntries = maximumEntryCount;
960 void WebResourceLoadStatisticsStore::setPruneEntriesDownTo(size_t pruneTargetCount)
962 m_parameters.pruneEntriesDownTo = pruneTargetCount;
965 struct StatisticsLastSeen {
966 String topPrivatelyOwnedDomain;
970 static void pruneResources(HashMap<String, WebCore::ResourceLoadStatistics>& statisticsMap, Vector<StatisticsLastSeen>& statisticsToPrune, size_t& numberOfEntriesToPrune)
972 if (statisticsToPrune.size() > numberOfEntriesToPrune) {
973 std::sort(statisticsToPrune.begin(), statisticsToPrune.end(), [](const StatisticsLastSeen& a, const StatisticsLastSeen& b) {
974 return a.lastSeen < b.lastSeen;
978 for (size_t i = 0, end = std::min(numberOfEntriesToPrune, statisticsToPrune.size()); i != end; ++i, --numberOfEntriesToPrune)
979 statisticsMap.remove(statisticsToPrune[i].topPrivatelyOwnedDomain);
982 static unsigned computeImportance(const ResourceLoadStatistics& resourceStatistic)
984 unsigned importance = maxImportance;
985 if (!resourceStatistic.isPrevalentResource)
987 if (!resourceStatistic.hadUserInteraction)
992 void WebResourceLoadStatisticsStore::pruneStatisticsIfNeeded()
994 ASSERT(!RunLoop::isMain());
995 if (m_resourceStatisticsMap.size() <= m_parameters.maxStatisticsEntries)
998 ASSERT(m_parameters.pruneEntriesDownTo <= m_parameters.maxStatisticsEntries);
1000 size_t numberOfEntriesLeftToPrune = m_resourceStatisticsMap.size() - m_parameters.pruneEntriesDownTo;
1001 ASSERT(numberOfEntriesLeftToPrune);
1003 Vector<StatisticsLastSeen> resourcesToPrunePerImportance[maxImportance + 1];
1004 for (auto& resourceStatistic : m_resourceStatisticsMap.values())
1005 resourcesToPrunePerImportance[computeImportance(resourceStatistic)].append({ resourceStatistic.highLevelDomain, resourceStatistic.lastSeen });
1007 for (unsigned importance = 0; numberOfEntriesLeftToPrune && importance <= maxImportance; ++importance)
1008 pruneResources(m_resourceStatisticsMap, resourcesToPrunePerImportance[importance], numberOfEntriesLeftToPrune);
1010 ASSERT(!numberOfEntriesLeftToPrune);
1013 void WebResourceLoadStatisticsStore::resetParametersToDefaultValues()
1018 void WebResourceLoadStatisticsStore::logTestingEvent(const String& event)
1020 if (!m_statisticsTestingCallback)
1023 if (RunLoop::isMain())
1024 m_statisticsTestingCallback(event);
1026 RunLoop::main().dispatch([this, protectedThis = makeRef(*this), event = event.isolatedCopy()] {
1027 if (m_statisticsTestingCallback)
1028 m_statisticsTestingCallback(event);
1033 } // namespace WebKit