2 * Copyright (C) 2016-2017 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::IndexedDBDatabases,
63 WebsiteDataType::LocalStorage,
64 WebsiteDataType::MediaKeys,
65 WebsiteDataType::OfflineWebApplicationCache,
66 #if ENABLE(NETSCAPE_PLUGIN_API)
67 WebsiteDataType::PlugInData,
69 WebsiteDataType::SearchFieldRecentSearches,
70 WebsiteDataType::SessionStorage,
71 WebsiteDataType::WebSQLDatabases,
74 ASSERT(RunLoop::isMain());
81 OperatingDate() = default;
83 static OperatingDate fromWallTime(WallTime time)
85 double ms = time.secondsSinceEpoch().milliseconds();
86 int year = msToYear(ms);
87 int yearDay = dayInYear(ms, year);
88 int month = monthFromDayInYear(yearDay, isLeapYear(year));
89 int monthDay = dayInMonthFromDayInYear(yearDay, isLeapYear(year));
91 return OperatingDate { year, month, monthDay };
94 static OperatingDate today()
96 return OperatingDate::fromWallTime(WallTime::now());
99 Seconds secondsSinceEpoch() const
101 return Seconds { dateToDaysFrom1970(m_year, m_month, m_monthDay) * secondsPerDay };
104 bool operator==(const OperatingDate& other) const
106 return m_monthDay == other.m_monthDay && m_month == other.m_month && m_year == other.m_year;
109 bool operator<(const OperatingDate& other) const
111 return secondsSinceEpoch() < other.secondsSinceEpoch();
114 bool operator<=(const OperatingDate& other) const
116 return secondsSinceEpoch() <= other.secondsSinceEpoch();
120 OperatingDate(int year, int month, int monthDay)
123 , m_monthDay(monthDay)
127 int m_month { 0 }; // [0, 11].
128 int m_monthDay { 0 }; // [1, 31].
131 static Vector<OperatingDate> mergeOperatingDates(const Vector<OperatingDate>& existingDates, Vector<OperatingDate>&& newDates)
133 if (existingDates.isEmpty())
134 return WTFMove(newDates);
136 Vector<OperatingDate> mergedDates(existingDates.size() + newDates.size());
138 // Merge the two sorted vectors of dates.
139 std::merge(existingDates.begin(), existingDates.end(), newDates.begin(), newDates.end(), mergedDates.begin());
140 // Remove duplicate dates.
141 removeRepeatedElements(mergedDates);
143 // Drop old dates until the Vector size reaches operatingDatesWindow.
144 while (mergedDates.size() > operatingDatesWindow)
145 mergedDates.remove(0);
150 WebResourceLoadStatisticsStore::WebResourceLoadStatisticsStore(const String& resourceLoadStatisticsDirectory, Function<void(const String&)>&& testingCallback, UpdatePrevalentDomainsToPartitionOrBlockCookiesHandler&& updatePrevalentDomainsToPartitionOrBlockCookiesHandler, UpdateStorageAccessForPrevalentDomainsHandler&& updateStorageAccessForPrevalentDomainsHandler, RemovePrevalentDomainsHandler&& removeDomainsHandler)
151 : m_statisticsQueue(WorkQueue::create("WebResourceLoadStatisticsStore Process Data Queue", WorkQueue::Type::Serial, WorkQueue::QOS::Utility))
152 , m_persistentStorage(*this, resourceLoadStatisticsDirectory)
153 , m_updatePrevalentDomainsToPartitionOrBlockCookiesHandler(WTFMove(updatePrevalentDomainsToPartitionOrBlockCookiesHandler))
154 , m_updateStorageAccessForPrevalentDomainsHandler(WTFMove(updateStorageAccessForPrevalentDomainsHandler))
155 , m_removeDomainsHandler(WTFMove(removeDomainsHandler))
156 , m_dailyTasksTimer(RunLoop::main(), this, &WebResourceLoadStatisticsStore::performDailyTasks)
157 , m_statisticsTestingCallback(WTFMove(testingCallback))
159 ASSERT(RunLoop::isMain());
162 registerUserDefaultsIfNeeded();
165 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
166 m_persistentStorage.initialize();
167 includeTodayAsOperatingDateIfNecessary();
170 m_statisticsQueue->dispatchAfter(5_s, [this, protectedThis = makeRef(*this)] {
171 if (m_parameters.shouldSubmitTelemetry)
172 WebResourceLoadStatisticsTelemetry::calculateAndSubmit(*this);
175 m_dailyTasksTimer.startRepeating(24_h);
178 WebResourceLoadStatisticsStore::~WebResourceLoadStatisticsStore()
180 m_persistentStorage.finishAllPendingWorkSynchronously();
183 void WebResourceLoadStatisticsStore::removeDataRecords()
185 ASSERT(!RunLoop::isMain());
187 if (!shouldRemoveDataRecords())
190 #if ENABLE(NETSCAPE_PLUGIN_API)
191 m_activePluginTokens.clear();
192 for (auto plugin : PluginProcessManager::singleton().pluginProcesses())
193 m_activePluginTokens.add(plugin->pluginProcessToken());
196 auto prevalentResourceDomains = topPrivatelyControlledDomainsToRemoveWebsiteDataFor();
197 if (prevalentResourceDomains.isEmpty())
200 setDataRecordsBeingRemoved(true);
202 RunLoop::main().dispatch([prevalentResourceDomains = crossThreadCopy(prevalentResourceDomains), this, protectedThis = makeRef(*this)] () mutable {
203 WebProcessProxy::deleteWebsiteDataForTopPrivatelyControlledDomainsInAllPersistentDataStores(WebResourceLoadStatisticsStore::monitoredDataTypes(), WTFMove(prevalentResourceDomains), m_parameters.shouldNotifyPagesWhenDataRecordsWereScanned, [this, protectedThis = WTFMove(protectedThis)](const HashSet<String>& domainsWithDeletedWebsiteData) mutable {
204 m_statisticsQueue->dispatch([this, protectedThis = WTFMove(protectedThis), topDomains = crossThreadCopy(domainsWithDeletedWebsiteData)] () mutable {
205 for (auto& prevalentResourceDomain : topDomains) {
206 auto& statistic = ensureResourceStatisticsForPrimaryDomain(prevalentResourceDomain);
207 ++statistic.dataRecordsRemoved;
209 setDataRecordsBeingRemoved(false);
215 void WebResourceLoadStatisticsStore::scheduleStatisticsAndDataRecordsProcessing()
217 ASSERT(RunLoop::isMain());
218 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
219 processStatisticsAndDataRecords();
223 void WebResourceLoadStatisticsStore::processStatisticsAndDataRecords()
225 ASSERT(!RunLoop::isMain());
227 if (m_parameters.shouldClassifyResourcesBeforeDataRecordsRemoval) {
228 for (auto& resourceStatistic : m_resourceStatisticsMap.values()) {
229 if (!resourceStatistic.isPrevalentResource && m_resourceLoadStatisticsClassifier.hasPrevalentResourceCharacteristics(resourceStatistic))
230 resourceStatistic.isPrevalentResource = true;
235 pruneStatisticsIfNeeded();
237 if (m_parameters.shouldNotifyPagesWhenDataRecordsWereScanned) {
238 RunLoop::main().dispatch([] {
239 WebProcessProxy::notifyPageStatisticsAndDataRecordsProcessed();
243 m_persistentStorage.scheduleOrWriteMemoryStore(ResourceLoadStatisticsPersistentStorage::ForceImmediateWrite::No);
246 void WebResourceLoadStatisticsStore::resourceLoadStatisticsUpdated(Vector<WebCore::ResourceLoadStatistics>&& origins)
248 ASSERT(!RunLoop::isMain());
250 mergeStatistics(WTFMove(origins));
251 // Fire before processing statistics to propagate user interaction as fast as possible to the network process.
252 updateCookiePartitioning();
253 processStatisticsAndDataRecords();
256 void WebResourceLoadStatisticsStore::hasStorageAccess(String&& subFrameHost, String&& topFrameHost, WTF::CompletionHandler<void (bool)>&& callback)
258 ASSERT(subFrameHost != topFrameHost);
259 ASSERT(RunLoop::isMain());
261 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), subFramePrimaryDomain = isolatedPrimaryDomain(subFrameHost), topFramePrimaryDomain = isolatedPrimaryDomain(topFrameHost), callback = WTFMove(callback)] () mutable {
263 auto& topFrameStatistic = ensureResourceStatisticsForPrimaryDomain(topFramePrimaryDomain);
264 if (topFrameStatistic.storageAccessUnderTopFrameOrigins.contains(subFramePrimaryDomain)) {
269 auto& subFrameStatistic = ensureResourceStatisticsForPrimaryDomain(subFramePrimaryDomain);
270 if (shouldBlockCookies(subFrameStatistic)) {
275 callback(!shouldPartitionCookies(subFrameStatistic));
279 void WebResourceLoadStatisticsStore::requestStorageAccess(String&& subFrameHost, String&& topFrameHost, uint64_t frameID, uint64_t pageID, WTF::CompletionHandler<void (bool)>&& callback)
281 ASSERT(subFrameHost != topFrameHost);
282 ASSERT(RunLoop::isMain());
284 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), subFramePrimaryDomain = isolatedPrimaryDomain(subFrameHost), topFramePrimaryDomain = isolatedPrimaryDomain(topFrameHost), frameID, pageID, callback = WTFMove(callback)] () mutable {
286 auto& subFrameStatistic = ensureResourceStatisticsForPrimaryDomain(subFramePrimaryDomain);
287 if (shouldBlockCookies(subFrameStatistic)) {
292 if (!shouldPartitionCookies(subFrameStatistic)) {
297 m_updateStorageAccessForPrevalentDomainsHandler(subFramePrimaryDomain, topFramePrimaryDomain, frameID, pageID, true, WTFMove(callback));
301 void WebResourceLoadStatisticsStore::grandfatherExistingWebsiteData()
303 ASSERT(!RunLoop::isMain());
305 RunLoop::main().dispatch([this, protectedThis = makeRef(*this)] () mutable {
306 // FIXME: This method being a static call on WebProcessProxy is wrong.
307 // It should be on the data store that this object belongs to.
308 WebProcessProxy::topPrivatelyControlledDomainsWithWebsiteData(WebResourceLoadStatisticsStore::monitoredDataTypes(), m_parameters.shouldNotifyPagesWhenDataRecordsWereScanned, [this, protectedThis = WTFMove(protectedThis)] (HashSet<String>&& topPrivatelyControlledDomainsWithWebsiteData) mutable {
309 m_statisticsQueue->dispatch([this, protectedThis = WTFMove(protectedThis), topDomains = crossThreadCopy(topPrivatelyControlledDomainsWithWebsiteData)] () mutable {
310 for (auto& topPrivatelyControlledDomain : topDomains) {
311 auto& statistic = ensureResourceStatisticsForPrimaryDomain(topPrivatelyControlledDomain);
312 statistic.grandfathered = true;
314 m_endOfGrandfatheringTimestamp = WallTime::now() + m_parameters.grandfatheringTime;
315 m_persistentStorage.scheduleOrWriteMemoryStore(ResourceLoadStatisticsPersistentStorage::ForceImmediateWrite::Yes);
318 logTestingEvent(ASCIILiteral("Grandfathered"));
323 void WebResourceLoadStatisticsStore::processWillOpenConnection(WebProcessProxy&, IPC::Connection& connection)
325 connection.addWorkQueueMessageReceiver(Messages::WebResourceLoadStatisticsStore::messageReceiverName(), m_statisticsQueue.get(), this);
328 void WebResourceLoadStatisticsStore::processDidCloseConnection(WebProcessProxy&, IPC::Connection& connection)
330 connection.removeWorkQueueMessageReceiver(Messages::WebResourceLoadStatisticsStore::messageReceiverName());
333 void WebResourceLoadStatisticsStore::applicationWillTerminate()
335 m_persistentStorage.finishAllPendingWorkSynchronously();
338 void WebResourceLoadStatisticsStore::performDailyTasks()
340 ASSERT(RunLoop::isMain());
342 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
343 includeTodayAsOperatingDateIfNecessary();
345 if (m_parameters.shouldSubmitTelemetry)
349 void WebResourceLoadStatisticsStore::submitTelemetry()
351 ASSERT(RunLoop::isMain());
352 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
353 WebResourceLoadStatisticsTelemetry::calculateAndSubmit(*this);
357 void WebResourceLoadStatisticsStore::logUserInteraction(const URL& url)
359 if (url.isBlankURL() || url.isEmpty())
362 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url)] {
363 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
364 statistics.hadUserInteraction = true;
365 statistics.mostRecentUserInteractionTime = WallTime::now();
367 updateCookiePartitioningForDomains({ }, { }, { primaryDomain }, ShouldClearFirst::No);
371 void WebResourceLoadStatisticsStore::logNonRecentUserInteraction(const URL& url)
373 if (url.isBlankURL() || url.isEmpty())
376 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url)] {
377 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
378 statistics.hadUserInteraction = true;
379 statistics.mostRecentUserInteractionTime = WallTime::now() - (m_parameters.timeToLiveCookiePartitionFree + Seconds::fromHours(1));
381 updateCookiePartitioningForDomains({ primaryDomain }, { }, { }, ShouldClearFirst::No);
385 void WebResourceLoadStatisticsStore::clearUserInteraction(const URL& url)
387 if (url.isBlankURL() || url.isEmpty())
390 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url)] {
391 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
392 statistics.hadUserInteraction = false;
393 statistics.mostRecentUserInteractionTime = { };
397 void WebResourceLoadStatisticsStore::hasHadUserInteraction(const URL& url, WTF::Function<void (bool)>&& completionHandler)
399 if (url.isBlankURL() || url.isEmpty()) {
400 completionHandler(false);
404 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url), completionHandler = WTFMove(completionHandler)] () mutable {
405 auto mapEntry = m_resourceStatisticsMap.find(primaryDomain);
406 bool hadUserInteraction = mapEntry == m_resourceStatisticsMap.end() ? false: hasHadUnexpiredRecentUserInteraction(mapEntry->value);
407 RunLoop::main().dispatch([hadUserInteraction, completionHandler = WTFMove(completionHandler)] {
408 completionHandler(hadUserInteraction);
413 void WebResourceLoadStatisticsStore::setLastSeen(const URL& url, Seconds seconds)
415 if (url.isBlankURL() || url.isEmpty())
418 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url), seconds] {
419 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
420 statistics.lastSeen = WallTime::fromRawSeconds(seconds.seconds());
424 void WebResourceLoadStatisticsStore::setPrevalentResource(const URL& url)
426 if (url.isBlankURL() || url.isEmpty())
429 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url)] {
430 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
431 statistics.isPrevalentResource = true;
435 void WebResourceLoadStatisticsStore::isPrevalentResource(const URL& url, WTF::Function<void (bool)>&& completionHandler)
437 if (url.isBlankURL() || url.isEmpty()) {
438 completionHandler(false);
442 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url), completionHandler = WTFMove(completionHandler)] () mutable {
443 auto mapEntry = m_resourceStatisticsMap.find(primaryDomain);
444 bool isPrevalentResource = mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.isPrevalentResource;
445 RunLoop::main().dispatch([isPrevalentResource, completionHandler = WTFMove(completionHandler)] {
446 completionHandler(isPrevalentResource);
451 void WebResourceLoadStatisticsStore::isRegisteredAsSubFrameUnder(const URL& subFrame, const URL& topFrame, WTF::Function<void (bool)>&& completionHandler)
453 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), subFramePrimaryDomain = isolatedPrimaryDomain(subFrame), topFramePrimaryDomain = isolatedPrimaryDomain(topFrame), completionHandler = WTFMove(completionHandler)] () mutable {
454 auto mapEntry = m_resourceStatisticsMap.find(subFramePrimaryDomain);
455 bool isRegisteredAsSubFrameUnder = mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.subframeUnderTopFrameOrigins.contains(topFramePrimaryDomain);
456 RunLoop::main().dispatch([isRegisteredAsSubFrameUnder, completionHandler = WTFMove(completionHandler)] {
457 completionHandler(isRegisteredAsSubFrameUnder);
462 void WebResourceLoadStatisticsStore::isRegisteredAsRedirectingTo(const URL& hostRedirectedFrom, const URL& hostRedirectedTo, WTF::Function<void (bool)>&& completionHandler)
464 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), hostRedirectedFromPrimaryDomain = isolatedPrimaryDomain(hostRedirectedFrom), hostRedirectedToPrimaryDomain = isolatedPrimaryDomain(hostRedirectedTo), completionHandler = WTFMove(completionHandler)] () mutable {
465 auto mapEntry = m_resourceStatisticsMap.find(hostRedirectedFromPrimaryDomain);
466 bool isRegisteredAsRedirectingTo = mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.subresourceUniqueRedirectsTo.contains(hostRedirectedToPrimaryDomain);
467 RunLoop::main().dispatch([isRegisteredAsRedirectingTo, completionHandler = WTFMove(completionHandler)] {
468 completionHandler(isRegisteredAsRedirectingTo);
473 void WebResourceLoadStatisticsStore::clearPrevalentResource(const URL& url)
475 if (url.isBlankURL() || url.isEmpty())
478 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url)] {
479 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
480 statistics.isPrevalentResource = false;
484 void WebResourceLoadStatisticsStore::setGrandfathered(const URL& url, bool value)
486 if (url.isBlankURL() || url.isEmpty())
489 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryDomain = isolatedPrimaryDomain(url), value] {
490 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primaryDomain);
491 statistics.grandfathered = value;
495 void WebResourceLoadStatisticsStore::isGrandfathered(const URL& url, WTF::Function<void (bool)>&& completionHandler)
497 if (url.isBlankURL() || url.isEmpty()) {
498 completionHandler(false);
502 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler), primaryDomain = isolatedPrimaryDomain(url)] () mutable {
503 auto mapEntry = m_resourceStatisticsMap.find(primaryDomain);
504 bool isGrandFathered = mapEntry == m_resourceStatisticsMap.end() ? false : mapEntry->value.grandfathered;
505 RunLoop::main().dispatch([isGrandFathered, completionHandler = WTFMove(completionHandler)] {
506 completionHandler(isGrandFathered);
511 void WebResourceLoadStatisticsStore::setSubframeUnderTopFrameOrigin(const URL& subframe, const URL& topFrame)
513 if (subframe.isBlankURL() || subframe.isEmpty() || topFrame.isBlankURL() || topFrame.isEmpty())
516 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryTopFrameDomain = isolatedPrimaryDomain(topFrame), primarySubFrameDomain = isolatedPrimaryDomain(subframe)] {
517 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primarySubFrameDomain);
518 statistics.subframeUnderTopFrameOrigins.add(primaryTopFrameDomain);
522 void WebResourceLoadStatisticsStore::setSubresourceUnderTopFrameOrigin(const URL& subresource, const URL& topFrame)
524 if (subresource.isBlankURL() || subresource.isEmpty() || topFrame.isBlankURL() || topFrame.isEmpty())
527 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryTopFrameDomain = isolatedPrimaryDomain(topFrame), primarySubresourceDomain = isolatedPrimaryDomain(subresource)] {
528 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primarySubresourceDomain);
529 statistics.subresourceUnderTopFrameOrigins.add(primaryTopFrameDomain);
533 void WebResourceLoadStatisticsStore::setSubresourceUniqueRedirectTo(const URL& subresource, const URL& hostNameRedirectedTo)
535 if (subresource.isBlankURL() || subresource.isEmpty() || hostNameRedirectedTo.isBlankURL() || hostNameRedirectedTo.isEmpty())
538 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), primaryRedirectDomain = isolatedPrimaryDomain(hostNameRedirectedTo), primarySubresourceDomain = isolatedPrimaryDomain(subresource)] {
539 auto& statistics = ensureResourceStatisticsForPrimaryDomain(primarySubresourceDomain);
540 statistics.subresourceUniqueRedirectsTo.add(primaryRedirectDomain);
544 void WebResourceLoadStatisticsStore::scheduleCookiePartitioningUpdate()
546 // Helper function used by testing system. Should only be called from the main thread.
547 ASSERT(RunLoop::isMain());
549 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
550 updateCookiePartitioning();
554 void WebResourceLoadStatisticsStore::scheduleCookiePartitioningUpdateForDomains(const Vector<String>& domainsToPartition, const Vector<String>& domainsToBlock, const Vector<String>& domainsToNeitherPartitionNorBlock, ShouldClearFirst shouldClearFirst)
556 // Helper function used by testing system. Should only be called from the main thread.
557 ASSERT(RunLoop::isMain());
558 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), domainsToPartition = crossThreadCopy(domainsToPartition), domainsToBlock = crossThreadCopy(domainsToBlock), domainsToNeitherPartitionNorBlock = crossThreadCopy(domainsToNeitherPartitionNorBlock), shouldClearFirst] {
559 updateCookiePartitioningForDomains(domainsToPartition, domainsToBlock, domainsToNeitherPartitionNorBlock, shouldClearFirst);
563 void WebResourceLoadStatisticsStore::scheduleClearPartitioningStateForDomains(const Vector<String>& domains)
565 // Helper function used by testing system. Should only be called from the main thread.
566 ASSERT(RunLoop::isMain());
567 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), domains = crossThreadCopy(domains)] {
568 clearPartitioningStateForDomains(domains);
572 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
573 void WebResourceLoadStatisticsStore::scheduleCookiePartitioningStateReset()
575 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
576 resetCookiePartitioningState();
581 void WebResourceLoadStatisticsStore::scheduleClearInMemory()
583 ASSERT(RunLoop::isMain());
584 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this)] {
589 void WebResourceLoadStatisticsStore::scheduleClearInMemoryAndPersistent(ShouldGrandfather shouldGrandfather)
591 ASSERT(RunLoop::isMain());
592 m_statisticsQueue->dispatch([this, protectedThis = makeRef(*this), shouldGrandfather] {
594 m_persistentStorage.clear();
596 if (shouldGrandfather == ShouldGrandfather::Yes)
597 grandfatherExistingWebsiteData();
601 void WebResourceLoadStatisticsStore::scheduleClearInMemoryAndPersistent(std::chrono::system_clock::time_point modifiedSince, ShouldGrandfather shouldGrandfather)
603 // For now, be conservative and clear everything regardless of modifiedSince.
604 UNUSED_PARAM(modifiedSince);
605 scheduleClearInMemoryAndPersistent(shouldGrandfather);
608 void WebResourceLoadStatisticsStore::setTimeToLiveUserInteraction(Seconds seconds)
610 ASSERT(seconds >= 0_s);
611 m_parameters.timeToLiveUserInteraction = seconds;
614 void WebResourceLoadStatisticsStore::setTimeToLiveCookiePartitionFree(Seconds seconds)
616 ASSERT(seconds >= 0_s);
617 m_parameters.timeToLiveCookiePartitionFree = seconds;
620 void WebResourceLoadStatisticsStore::setMinimumTimeBetweenDataRecordsRemoval(Seconds seconds)
622 ASSERT(seconds >= 0_s);
623 m_parameters.minimumTimeBetweenDataRecordsRemoval = seconds;
626 void WebResourceLoadStatisticsStore::setGrandfatheringTime(Seconds seconds)
628 ASSERT(seconds >= 0_s);
629 m_parameters.grandfatheringTime = seconds;
632 bool WebResourceLoadStatisticsStore::shouldRemoveDataRecords() const
634 ASSERT(!RunLoop::isMain());
635 if (m_dataRecordsBeingRemoved)
638 #if ENABLE(NETSCAPE_PLUGIN_API)
639 for (auto plugin : PluginProcessManager::singleton().pluginProcesses()) {
640 if (!m_activePluginTokens.contains(plugin->pluginProcessToken()))
645 return !m_lastTimeDataRecordsWereRemoved || MonotonicTime::now() >= (m_lastTimeDataRecordsWereRemoved + m_parameters.minimumTimeBetweenDataRecordsRemoval);
648 void WebResourceLoadStatisticsStore::setDataRecordsBeingRemoved(bool value)
650 ASSERT(!RunLoop::isMain());
651 m_dataRecordsBeingRemoved = value;
652 if (m_dataRecordsBeingRemoved)
653 m_lastTimeDataRecordsWereRemoved = MonotonicTime::now();
656 ResourceLoadStatistics& WebResourceLoadStatisticsStore::ensureResourceStatisticsForPrimaryDomain(const String& primaryDomain)
658 ASSERT(!RunLoop::isMain());
659 return m_resourceStatisticsMap.ensure(primaryDomain, [&primaryDomain] {
660 return ResourceLoadStatistics(primaryDomain);
664 std::unique_ptr<KeyedEncoder> WebResourceLoadStatisticsStore::createEncoderFromData() const
666 ASSERT(!RunLoop::isMain());
667 auto encoder = KeyedEncoder::encoder();
668 encoder->encodeUInt32("version", statisticsModelVersion);
669 encoder->encodeDouble("endOfGrandfatheringTimestamp", m_endOfGrandfatheringTimestamp.secondsSinceEpoch().value());
671 encoder->encodeObjects("browsingStatistics", m_resourceStatisticsMap.begin(), m_resourceStatisticsMap.end(), [](KeyedEncoder& encoderInner, const auto& origin) {
672 origin.value.encode(encoderInner);
675 encoder->encodeObjects("operatingDates", m_operatingDates.begin(), m_operatingDates.end(), [](KeyedEncoder& encoderInner, OperatingDate date) {
676 encoderInner.encodeDouble("date", date.secondsSinceEpoch().value());
682 void WebResourceLoadStatisticsStore::mergeWithDataFromDecoder(KeyedDecoder& decoder)
684 ASSERT(!RunLoop::isMain());
686 unsigned versionOnDisk;
687 if (!decoder.decodeUInt32("version", versionOnDisk))
690 if (versionOnDisk != statisticsModelVersion)
693 double endOfGrandfatheringTimestamp;
694 if (decoder.decodeDouble("endOfGrandfatheringTimestamp", endOfGrandfatheringTimestamp))
695 m_endOfGrandfatheringTimestamp = WallTime::fromRawSeconds(endOfGrandfatheringTimestamp);
697 m_endOfGrandfatheringTimestamp = { };
699 Vector<ResourceLoadStatistics> loadedStatistics;
700 bool succeeded = decoder.decodeObjects("browsingStatistics", loadedStatistics, [](KeyedDecoder& decoderInner, ResourceLoadStatistics& statistics) {
701 return statistics.decode(decoderInner);
707 mergeStatistics(WTFMove(loadedStatistics));
708 updateCookiePartitioning();
710 Vector<OperatingDate> operatingDates;
711 succeeded = decoder.decodeObjects("operatingDates", operatingDates, [](KeyedDecoder& decoder, OperatingDate& date) {
713 if (!decoder.decodeDouble("date", value))
716 date = OperatingDate::fromWallTime(WallTime::fromRawSeconds(value));
723 m_operatingDates = mergeOperatingDates(m_operatingDates, WTFMove(operatingDates));
726 void WebResourceLoadStatisticsStore::clearInMemory()
728 ASSERT(!RunLoop::isMain());
729 m_resourceStatisticsMap.clear();
730 m_operatingDates.clear();
732 updateCookiePartitioningForDomains({ }, { }, { }, ShouldClearFirst::Yes);
735 void WebResourceLoadStatisticsStore::mergeStatistics(Vector<ResourceLoadStatistics>&& statistics)
737 ASSERT(!RunLoop::isMain());
738 for (auto& statistic : statistics) {
739 auto result = m_resourceStatisticsMap.ensure(statistic.highLevelDomain, [&statistic] {
740 return WTFMove(statistic);
742 if (!result.isNewEntry)
743 result.iterator->value.merge(statistic);
747 bool WebResourceLoadStatisticsStore::shouldPartitionCookies(const ResourceLoadStatistics& statistic) const
749 return statistic.isPrevalentResource && statistic.hadUserInteraction && WallTime::now() > statistic.mostRecentUserInteractionTime + m_parameters.timeToLiveCookiePartitionFree;
752 bool WebResourceLoadStatisticsStore::shouldBlockCookies(const ResourceLoadStatistics& statistic) const
754 return statistic.isPrevalentResource && !statistic.hadUserInteraction;
757 void WebResourceLoadStatisticsStore::updateCookiePartitioning()
759 ASSERT(!RunLoop::isMain());
761 Vector<String> domainsToPartition;
762 Vector<String> domainsToBlock;
763 Vector<String> domainsToNeitherPartitionNorBlock;
764 for (auto& resourceStatistic : m_resourceStatisticsMap.values()) {
766 bool shouldPartition = shouldPartitionCookies(resourceStatistic);
767 bool shouldBlock = shouldBlockCookies(resourceStatistic);
769 if (shouldPartition && !resourceStatistic.isMarkedForCookiePartitioning) {
770 domainsToPartition.append(resourceStatistic.highLevelDomain);
771 resourceStatistic.isMarkedForCookiePartitioning = true;
772 } else if (shouldBlock && !resourceStatistic.isMarkedForCookieBlocking) {
773 domainsToBlock.append(resourceStatistic.highLevelDomain);
774 resourceStatistic.isMarkedForCookieBlocking = true;
775 } else if (!shouldPartition && !shouldBlock && resourceStatistic.isPrevalentResource) {
776 domainsToNeitherPartitionNorBlock.append(resourceStatistic.highLevelDomain);
777 resourceStatistic.isMarkedForCookiePartitioning = false;
778 resourceStatistic.isMarkedForCookieBlocking = false;
782 if (domainsToPartition.isEmpty() && domainsToBlock.isEmpty() && domainsToNeitherPartitionNorBlock.isEmpty())
785 RunLoop::main().dispatch([this, protectedThis = makeRef(*this), domainsToPartition = crossThreadCopy(domainsToPartition), domainsToBlock = crossThreadCopy(domainsToBlock), domainsToNeitherPartitionNorBlock = crossThreadCopy(domainsToNeitherPartitionNorBlock)] () {
786 m_updatePrevalentDomainsToPartitionOrBlockCookiesHandler(domainsToPartition, domainsToBlock, domainsToNeitherPartitionNorBlock, ShouldClearFirst::No);
790 void WebResourceLoadStatisticsStore::updateCookiePartitioningForDomains(const Vector<String>& domainsToPartition, const Vector<String>& domainsToBlock, const Vector<String>& domainsToNeitherPartitionNorBlock, ShouldClearFirst shouldClearFirst)
792 ASSERT(!RunLoop::isMain());
793 if (domainsToPartition.isEmpty() && domainsToBlock.isEmpty() && domainsToNeitherPartitionNorBlock.isEmpty() && shouldClearFirst == ShouldClearFirst::No)
796 RunLoop::main().dispatch([this, shouldClearFirst, protectedThis = makeRef(*this), domainsToPartition = crossThreadCopy(domainsToPartition), domainsToBlock = crossThreadCopy(domainsToBlock), domainsToNeitherPartitionNorBlock = crossThreadCopy(domainsToNeitherPartitionNorBlock)] () {
797 m_updatePrevalentDomainsToPartitionOrBlockCookiesHandler(domainsToPartition, domainsToBlock, domainsToNeitherPartitionNorBlock, shouldClearFirst);
800 if (shouldClearFirst == ShouldClearFirst::Yes)
801 resetCookiePartitioningState();
803 for (auto& domain : domainsToNeitherPartitionNorBlock) {
804 auto& statistic = ensureResourceStatisticsForPrimaryDomain(domain);
805 statistic.isMarkedForCookiePartitioning = false;
806 statistic.isMarkedForCookieBlocking = false;
810 for (auto& domain : domainsToPartition)
811 ensureResourceStatisticsForPrimaryDomain(domain).isMarkedForCookiePartitioning = true;
813 for (auto& domain : domainsToBlock)
814 ensureResourceStatisticsForPrimaryDomain(domain).isMarkedForCookieBlocking = true;
817 void WebResourceLoadStatisticsStore::clearPartitioningStateForDomains(const Vector<String>& domains)
819 ASSERT(!RunLoop::isMain());
820 if (domains.isEmpty())
823 RunLoop::main().dispatch([this, protectedThis = makeRef(*this), domains = crossThreadCopy(domains)] () {
824 m_removeDomainsHandler(domains);
827 for (auto& domain : domains) {
828 auto& statistic = ensureResourceStatisticsForPrimaryDomain(domain);
829 statistic.isMarkedForCookiePartitioning = false;
830 statistic.isMarkedForCookieBlocking = false;
834 void WebResourceLoadStatisticsStore::resetCookiePartitioningState()
836 ASSERT(!RunLoop::isMain());
837 for (auto& resourceStatistic : m_resourceStatisticsMap.values()) {
838 resourceStatistic.isMarkedForCookiePartitioning = false;
839 resourceStatistic.isMarkedForCookieBlocking = false;
843 void WebResourceLoadStatisticsStore::processStatistics(const WTF::Function<void (const ResourceLoadStatistics&)>& processFunction) const
845 ASSERT(!RunLoop::isMain());
846 for (auto& resourceStatistic : m_resourceStatisticsMap.values())
847 processFunction(resourceStatistic);
850 bool WebResourceLoadStatisticsStore::hasHadUnexpiredRecentUserInteraction(ResourceLoadStatistics& resourceStatistic) const
852 if (resourceStatistic.hadUserInteraction && hasStatisticsExpired(resourceStatistic)) {
853 // Drop privacy sensitive data because we no longer need it.
854 // Set timestamp to 0 so that statistics merge will know
855 // it has been reset as opposed to its default -1.
856 resourceStatistic.mostRecentUserInteractionTime = { };
857 resourceStatistic.hadUserInteraction = false;
860 return resourceStatistic.hadUserInteraction;
863 Vector<String> WebResourceLoadStatisticsStore::topPrivatelyControlledDomainsToRemoveWebsiteDataFor()
865 ASSERT(!RunLoop::isMain());
867 bool shouldCheckForGrandfathering = m_endOfGrandfatheringTimestamp > WallTime::now();
868 bool shouldClearGrandfathering = !shouldCheckForGrandfathering && m_endOfGrandfatheringTimestamp;
870 if (shouldClearGrandfathering)
871 m_endOfGrandfatheringTimestamp = { };
873 Vector<String> prevalentResources;
874 for (auto& statistic : m_resourceStatisticsMap.values()) {
875 if (statistic.isPrevalentResource && !hasHadUnexpiredRecentUserInteraction(statistic) && (!shouldCheckForGrandfathering || !statistic.grandfathered))
876 prevalentResources.append(statistic.highLevelDomain);
878 if (shouldClearGrandfathering && statistic.grandfathered)
879 statistic.grandfathered = false;
882 return prevalentResources;
885 void WebResourceLoadStatisticsStore::includeTodayAsOperatingDateIfNecessary()
887 ASSERT(!RunLoop::isMain());
889 auto today = OperatingDate::today();
890 if (!m_operatingDates.isEmpty() && today <= m_operatingDates.last())
893 while (m_operatingDates.size() >= operatingDatesWindow)
894 m_operatingDates.remove(0);
896 m_operatingDates.append(today);
899 bool WebResourceLoadStatisticsStore::hasStatisticsExpired(const ResourceLoadStatistics& resourceStatistic) const
901 if (m_operatingDates.size() >= operatingDatesWindow) {
902 if (OperatingDate::fromWallTime(resourceStatistic.mostRecentUserInteractionTime) < m_operatingDates.first())
906 // If we don't meet the real criteria for an expired statistic, check the user setting for a tighter restriction (mainly for testing).
907 if (m_parameters.timeToLiveUserInteraction) {
908 if (WallTime::now() > resourceStatistic.mostRecentUserInteractionTime + m_parameters.timeToLiveUserInteraction.value())
915 void WebResourceLoadStatisticsStore::setMaxStatisticsEntries(size_t maximumEntryCount)
917 m_parameters.maxStatisticsEntries = maximumEntryCount;
920 void WebResourceLoadStatisticsStore::setPruneEntriesDownTo(size_t pruneTargetCount)
922 m_parameters.pruneEntriesDownTo = pruneTargetCount;
925 struct StatisticsLastSeen {
926 String topPrivatelyOwnedDomain;
930 static void pruneResources(HashMap<String, WebCore::ResourceLoadStatistics>& statisticsMap, Vector<StatisticsLastSeen>& statisticsToPrune, size_t& numberOfEntriesToPrune)
932 if (statisticsToPrune.size() > numberOfEntriesToPrune) {
933 std::sort(statisticsToPrune.begin(), statisticsToPrune.end(), [](const StatisticsLastSeen& a, const StatisticsLastSeen& b) {
934 return a.lastSeen < b.lastSeen;
938 for (size_t i = 0, end = std::min(numberOfEntriesToPrune, statisticsToPrune.size()); i != end; ++i, --numberOfEntriesToPrune)
939 statisticsMap.remove(statisticsToPrune[i].topPrivatelyOwnedDomain);
942 static unsigned computeImportance(const ResourceLoadStatistics& resourceStatistic)
944 unsigned importance = maxImportance;
945 if (!resourceStatistic.isPrevalentResource)
947 if (!resourceStatistic.hadUserInteraction)
952 void WebResourceLoadStatisticsStore::pruneStatisticsIfNeeded()
954 ASSERT(!RunLoop::isMain());
955 if (m_resourceStatisticsMap.size() <= m_parameters.maxStatisticsEntries)
958 ASSERT(m_parameters.pruneEntriesDownTo <= m_parameters.maxStatisticsEntries);
960 size_t numberOfEntriesLeftToPrune = m_resourceStatisticsMap.size() - m_parameters.pruneEntriesDownTo;
961 ASSERT(numberOfEntriesLeftToPrune);
963 Vector<StatisticsLastSeen> resourcesToPrunePerImportance[maxImportance + 1];
964 for (auto& resourceStatistic : m_resourceStatisticsMap.values())
965 resourcesToPrunePerImportance[computeImportance(resourceStatistic)].append({ resourceStatistic.highLevelDomain, resourceStatistic.lastSeen });
967 for (unsigned importance = 0; numberOfEntriesLeftToPrune && importance <= maxImportance; ++importance)
968 pruneResources(m_resourceStatisticsMap, resourcesToPrunePerImportance[importance], numberOfEntriesLeftToPrune);
970 ASSERT(!numberOfEntriesLeftToPrune);
973 void WebResourceLoadStatisticsStore::resetParametersToDefaultValues()
978 void WebResourceLoadStatisticsStore::logTestingEvent(const String& event)
980 if (!m_statisticsTestingCallback)
983 if (RunLoop::isMain())
984 m_statisticsTestingCallback(event);
986 RunLoop::main().dispatch([this, protectedThis = makeRef(*this), event = event.isolatedCopy()] {
987 if (m_statisticsTestingCallback)
988 m_statisticsTestingCallback(event);
993 } // namespace WebKit