blob: 70b5afda526e9909b962d71213d85b60b537181d [file] [log] [blame]
/*
* Copyright (C) 2016-2020 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "WebResourceLoadStatisticsStore.h"
#if ENABLE(INTELLIGENT_TRACKING_PREVENTION)
#include "APIDictionary.h"
#include "Logging.h"
#include "NetworkProcess.h"
#include "NetworkProcessProxyMessages.h"
#include "NetworkSession.h"
#include "PrivateClickMeasurementManager.h"
#include "ResourceLoadStatisticsDatabaseStore.h"
#include "ShouldGrandfatherStatistics.h"
#include "StorageAccessStatus.h"
#include "WebFrameProxy.h"
#include "WebPageProxy.h"
#include "WebProcessMessages.h"
#include "WebProcessPool.h"
#include "WebProcessProxy.h"
#include "WebsiteDataFetchOption.h"
#include <WebCore/CookieJar.h>
#include <WebCore/DiagnosticLoggingClient.h>
#include <WebCore/DiagnosticLoggingKeys.h>
#include <WebCore/NetworkStorageSession.h>
#include <WebCore/ResourceLoadStatistics.h>
#include <WebCore/SQLiteDatabase.h>
#include <WebCore/SQLiteFileSystem.h>
#include <WebCore/SQLiteStatement.h>
#include <wtf/CallbackAggregator.h>
#include <wtf/CrossThreadCopier.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/SuspendableWorkQueue.h>
#include <wtf/threads/BinarySemaphore.h>
namespace WebKit {
using namespace WebCore;
const OptionSet<WebsiteDataType>& WebResourceLoadStatisticsStore::monitoredDataTypes()
{
static NeverDestroyed<OptionSet<WebsiteDataType>> dataTypes(std::initializer_list<WebsiteDataType>({
WebsiteDataType::Cookies,
WebsiteDataType::DOMCache,
WebsiteDataType::IndexedDBDatabases,
WebsiteDataType::LocalStorage,
WebsiteDataType::MediaKeys,
WebsiteDataType::OfflineWebApplicationCache,
WebsiteDataType::SearchFieldRecentSearches,
WebsiteDataType::SessionStorage,
#if ENABLE(SERVICE_WORKER)
WebsiteDataType::ServiceWorkerRegistrations,
#endif
WebsiteDataType::FileSystem,
}));
ASSERT(RunLoop::isMain());
return dataTypes;
}
void WebResourceLoadStatisticsStore::setNotifyPagesWhenDataRecordsWereScanned(bool value)
{
ASSERT(RunLoop::isMain());
if (isEphemeral())
return;
postTask([this, value] {
if (m_statisticsStore)
m_statisticsStore->setNotifyPagesWhenDataRecordsWereScanned(value);
});
}
void WebResourceLoadStatisticsStore::setNotifyPagesWhenDataRecordsWereScanned(bool value, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (isEphemeral()) {
completionHandler();
return;
}
postTask([this, value, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->setNotifyPagesWhenDataRecordsWereScanned(value);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::setIsRunningTest(bool value, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (isEphemeral()) {
completionHandler();
return;
}
postTask([this, value, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->setIsRunningTest(value);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::setShouldClassifyResourcesBeforeDataRecordsRemoval(bool value, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, value, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->setShouldClassifyResourcesBeforeDataRecordsRemoval(value);
postTaskReply(WTFMove(completionHandler));
});
}
static Ref<SuspendableWorkQueue> sharedStatisticsQueue()
{
static NeverDestroyed<Ref<SuspendableWorkQueue>> queue(SuspendableWorkQueue::create("WebResourceLoadStatisticsStore Process Data Queue", WorkQueue::QOS::Utility));
return queue.get().copyRef();
}
WebResourceLoadStatisticsStore::WebResourceLoadStatisticsStore(NetworkSession& networkSession, const String& resourceLoadStatisticsDirectory, ShouldIncludeLocalhost shouldIncludeLocalhost, ResourceLoadStatistics::IsEphemeral isEphemeral)
: m_networkSession(networkSession)
, m_statisticsQueue(sharedStatisticsQueue())
, m_dailyTasksTimer(RunLoop::main(), this, &WebResourceLoadStatisticsStore::performDailyTasks)
, m_isEphemeral(isEphemeral)
{
RELEASE_ASSERT(RunLoop::isMain());
// No daily tasks needed for ephemeral sessions since no resource load statistics are collected.
if (isEphemeral == ResourceLoadStatistics::IsEphemeral::Yes)
return;
if (!resourceLoadStatisticsDirectory.isEmpty()) {
postTask([this, resourceLoadStatisticsDirectory = resourceLoadStatisticsDirectory.isolatedCopy(), shouldIncludeLocalhost, sessionID = networkSession.sessionID()] {
m_statisticsStore = makeUnique<ResourceLoadStatisticsDatabaseStore>(*this, m_statisticsQueue, shouldIncludeLocalhost, resourceLoadStatisticsDirectory, sessionID);
auto legacyPlistFilePath = FileSystem::pathByAppendingComponent(resourceLoadStatisticsDirectory, "full_browsing_session_resourceLog.plist");
if (FileSystem::fileExists(legacyPlistFilePath))
FileSystem::deleteFile(legacyPlistFilePath);
m_statisticsStore->didCreateNetworkProcess();
});
m_dailyTasksTimer.startRepeating(24_h);
}
}
WebResourceLoadStatisticsStore::~WebResourceLoadStatisticsStore()
{
RELEASE_ASSERT(RunLoop::isMain());
RELEASE_ASSERT(!m_statisticsStore);
}
Ref<WebResourceLoadStatisticsStore> WebResourceLoadStatisticsStore::create(NetworkSession& networkSession, const String& resourceLoadStatisticsDirectory, ShouldIncludeLocalhost shouldIncludeLocalhost, WebCore::ResourceLoadStatistics::IsEphemeral isEphemeral)
{
return adoptRef(*new WebResourceLoadStatisticsStore(networkSession, resourceLoadStatisticsDirectory, shouldIncludeLocalhost, isEphemeral));
}
void WebResourceLoadStatisticsStore::didDestroyNetworkSession(CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
auto callbackAggregator = CallbackAggregator::create([completionHandler = WTFMove(completionHandler)] () mutable {
completionHandler();
});
m_networkSession = nullptr;
destroyResourceLoadStatisticsStore([callbackAggregator] { });
}
inline void WebResourceLoadStatisticsStore::postTask(WTF::Function<void()>&& task)
{
// Resource load statistics should not be captured for ephemeral sessions.
RELEASE_ASSERT(!isEphemeral());
ASSERT(RunLoop::isMain());
m_statisticsQueue->dispatch([protectedThis = Ref { *this }, task = WTFMove(task)] {
task();
});
}
inline void WebResourceLoadStatisticsStore::postTaskReply(WTF::Function<void()>&& reply)
{
ASSERT(!RunLoop::isMain());
RunLoop::main().dispatch(WTFMove(reply));
}
void WebResourceLoadStatisticsStore::destroyResourceLoadStatisticsStore(CompletionHandler<void()>&& completionHandler)
{
RELEASE_ASSERT(RunLoop::isMain());
if (isEphemeral()) {
completionHandler();
return;
}
postTask([this, protectedThis = Ref { *this }, completionHandler = WTFMove(completionHandler)]() mutable {
m_statisticsStore = nullptr;
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::populateMemoryStoreFromDisk(CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, protectedThis = Ref { *this }, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore && is<ResourceLoadStatisticsDatabaseStore>(*m_statisticsStore)) {
auto& databaseStore = downcast<ResourceLoadStatisticsDatabaseStore>(*m_statisticsStore);
if (databaseStore.isNewResourceLoadStatisticsDatabaseFile()) {
m_statisticsStore->grandfatherExistingWebsiteData([protectedThis = WTFMove(protectedThis), completionHandler = WTFMove(completionHandler)]() mutable {
postTaskReply(WTFMove(completionHandler));
});
databaseStore.setIsNewResourceLoadStatisticsDatabaseFile(false);
} else
postTaskReply([this, protectedThis = WTFMove(protectedThis), completionHandler = WTFMove(completionHandler)]() mutable {
logTestingEvent("PopulatedWithoutGrandfathering"_s);
completionHandler();
});
} else
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::setResourceLoadStatisticsDebugMode(bool value, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (isEphemeral()) {
completionHandler();
return;
}
if (m_networkSession) {
if (auto* storageSession = m_networkSession->networkStorageSession())
storageSession->setResourceLoadStatisticsDebugLoggingEnabled(value);
}
postTask([this, value, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->setResourceLoadStatisticsDebugMode(value);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::setPrevalentResourceForDebugMode(const RegistrableDomain& domain, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (isEphemeral()) {
completionHandler();
return;
}
postTask([this, domain = domain.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->setPrevalentResourceForDebugMode(domain);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::scheduleStatisticsAndDataRecordsProcessing(CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->processStatisticsAndDataRecords();
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::statisticsDatabaseHasAllTables(CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, completionHandler = WTFMove(completionHandler)]() mutable {
if (!m_statisticsStore || !is<ResourceLoadStatisticsDatabaseStore>(*m_statisticsStore)) {
completionHandler(false);
ASSERT_NOT_REACHED();
return;
}
auto missingTables = downcast<ResourceLoadStatisticsDatabaseStore>(*m_statisticsStore).checkForMissingTablesInSchema();
postTaskReply([hasAllTables = missingTables ? false : true, completionHandler = WTFMove(completionHandler)] () mutable {
completionHandler(hasAllTables);
});
});
}
void WebResourceLoadStatisticsStore::resourceLoadStatisticsUpdated(Vector<ResourceLoadStatistics>&& statistics, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
// It is safe to move the origins to the background queue without isolated copy here because this is an r-value
// coming from IPC. ResourceLoadStatistics only contains strings which are safe to move to other threads as long
// as nobody on this thread holds a reference to those strings.
postTask([this, protectedThis = Ref { *this }, statistics = WTFMove(statistics), completionHandler = WTFMove(completionHandler)]() mutable {
if (!m_statisticsStore) {
postTaskReply(WTFMove(completionHandler));
return;
}
m_statisticsStore->mergeStatistics(WTFMove(statistics));
postTaskReply(WTFMove(completionHandler));
// We can cancel any pending request to process statistics since we're doing it synchronously below.
m_statisticsStore->cancelPendingStatisticsProcessingRequest();
// Fire before processing statistics to propagate user interaction as fast as possible to the network process.
m_statisticsStore->updateCookieBlocking([this, protectedThis]() {
postTaskReply([this, protectedThis = protectedThis]() {
logTestingEvent("Statistics Updated"_s);
});
});
m_statisticsStore->processStatisticsAndDataRecords();
});
}
void WebResourceLoadStatisticsStore::hasStorageAccess(const RegistrableDomain& subFrameDomain, const RegistrableDomain& topFrameDomain, std::optional<FrameIdentifier> frameID, PageIdentifier pageID, CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT(subFrameDomain != topFrameDomain);
ASSERT(RunLoop::isMain());
if (isEphemeral())
return hasStorageAccessEphemeral(subFrameDomain, topFrameDomain, frameID, pageID, WTFMove(completionHandler));
postTask([this, subFrameDomain = subFrameDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy(), frameID, pageID, completionHandler = WTFMove(completionHandler)]() mutable {
if (!m_statisticsStore) {
postTaskReply([completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(false);
});
return;
}
m_statisticsStore->hasStorageAccess(subFrameDomain, topFrameDomain, frameID, pageID, [completionHandler = WTFMove(completionHandler)](bool hasStorageAccess) mutable {
postTaskReply([completionHandler = WTFMove(completionHandler), hasStorageAccess]() mutable {
completionHandler(hasStorageAccess);
});
});
});
}
void WebResourceLoadStatisticsStore::hasStorageAccessEphemeral(const RegistrableDomain& subFrameDomain, const RegistrableDomain& topFrameDomain, std::optional<FrameIdentifier> frameID, PageIdentifier pageID, CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT(isEphemeral());
if (m_networkSession) {
if (auto* storageSession = m_networkSession->networkStorageSession()) {
completionHandler(storageSession->hasStorageAccess(subFrameDomain, topFrameDomain, frameID, pageID));
return;
}
}
completionHandler(false);
}
bool WebResourceLoadStatisticsStore::hasStorageAccessForFrame(const RegistrableDomain& resourceDomain, const RegistrableDomain& firstPartyDomain, FrameIdentifier frameID, PageIdentifier pageID)
{
ASSERT(RunLoop::isMain());
if (!m_networkSession)
return false;
if (auto* storageSession = m_networkSession->networkStorageSession())
return storageSession->hasStorageAccess(resourceDomain, firstPartyDomain, frameID, pageID);
return false;
}
void WebResourceLoadStatisticsStore::callHasStorageAccessForFrameHandler(const RegistrableDomain& resourceDomain, const RegistrableDomain& firstPartyDomain, FrameIdentifier frameID, PageIdentifier pageID, CompletionHandler<void(bool hasAccess)>&& callback)
{
ASSERT(RunLoop::isMain());
if (m_networkSession) {
if (auto* storageSession = m_networkSession->networkStorageSession()) {
callback(storageSession->hasStorageAccess(resourceDomain, firstPartyDomain, frameID, pageID));
return;
}
}
callback(false);
}
void WebResourceLoadStatisticsStore::requestStorageAccess(const RegistrableDomain& subFrameDomain, const RegistrableDomain& topFrameDomain, FrameIdentifier frameID, PageIdentifier webPageID, WebPageProxyIdentifier webPageProxyID, StorageAccessScope scope, CompletionHandler<void(RequestStorageAccessResult)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (subFrameDomain == topFrameDomain) {
completionHandler({ StorageAccessWasGranted::Yes, StorageAccessPromptWasShown::No, scope, topFrameDomain, subFrameDomain });
return;
}
if (isEphemeral())
return requestStorageAccessEphemeral(subFrameDomain, topFrameDomain, frameID, webPageID, webPageProxyID, scope, WTFMove(completionHandler));
auto statusHandler = [this, protectedThis = Ref { *this }, subFrameDomain = subFrameDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy(), frameID, webPageID, webPageProxyID, scope, completionHandler = WTFMove(completionHandler)](StorageAccessStatus status) mutable {
switch (status) {
case StorageAccessStatus::CannotRequestAccess:
completionHandler({ StorageAccessWasGranted::No, StorageAccessPromptWasShown::No, scope, topFrameDomain, subFrameDomain });
return;
case StorageAccessStatus::RequiresUserPrompt:
{
if (!m_networkSession)
return completionHandler({ StorageAccessWasGranted::No, StorageAccessPromptWasShown::No, scope, topFrameDomain, subFrameDomain });
CompletionHandler<void(bool)> requestConfirmationCompletionHandler = [this, protectedThis, subFrameDomain, topFrameDomain, frameID, webPageID, scope, completionHandler = WTFMove(completionHandler)] (bool userDidGrantAccess) mutable {
if (userDidGrantAccess)
grantStorageAccess(subFrameDomain, topFrameDomain, frameID, webPageID, StorageAccessPromptWasShown::Yes, scope, WTFMove(completionHandler));
else
completionHandler({ StorageAccessWasGranted::No, StorageAccessPromptWasShown::Yes, scope, topFrameDomain, subFrameDomain });
};
m_networkSession->networkProcess().parentProcessConnection()->sendWithAsyncReply(Messages::NetworkProcessProxy::RequestStorageAccessConfirm(webPageProxyID, frameID, subFrameDomain, topFrameDomain), WTFMove(requestConfirmationCompletionHandler));
}
return;
case StorageAccessStatus::HasAccess:
completionHandler({ StorageAccessWasGranted::Yes, StorageAccessPromptWasShown::No, scope, topFrameDomain, subFrameDomain });
return;
}
};
postTask([this, subFrameDomain = subFrameDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy(), frameID, webPageID, scope, statusHandler = WTFMove(statusHandler)]() mutable {
if (!m_statisticsStore) {
postTaskReply([statusHandler = WTFMove(statusHandler)]() mutable {
statusHandler(StorageAccessStatus::CannotRequestAccess);
});
return;
}
m_statisticsStore->requestStorageAccess(WTFMove(subFrameDomain), WTFMove(topFrameDomain), frameID, webPageID, scope, [statusHandler = WTFMove(statusHandler)](StorageAccessStatus status) mutable {
postTaskReply([statusHandler = WTFMove(statusHandler), status]() mutable {
statusHandler(status);
});
});
});
}
void WebResourceLoadStatisticsStore::requestStorageAccessEphemeral(const RegistrableDomain& subFrameDomain, const RegistrableDomain& topFrameDomain, FrameIdentifier frameID, PageIdentifier webPageID, WebPageProxyIdentifier webPageProxyID, StorageAccessScope scope, CompletionHandler<void(RequestStorageAccessResult)>&& completionHandler)
{
ASSERT(isEphemeral());
if (!m_networkSession || (!m_domainsWithEphemeralUserInteraction.contains(subFrameDomain) && !NetworkStorageSession::canRequestStorageAccessForLoginOrCompatibilityPurposesWithoutPriorUserInteraction(subFrameDomain, topFrameDomain)))
return completionHandler({ StorageAccessWasGranted::No, StorageAccessPromptWasShown::No, scope, topFrameDomain, subFrameDomain });
CompletionHandler<void(bool)> requestConfirmationCompletionHandler = [this, protectedThis = Ref { *this }, subFrameDomain, topFrameDomain, frameID, webPageID, scope, completionHandler = WTFMove(completionHandler)] (bool userDidGrantAccess) mutable {
if (userDidGrantAccess)
grantStorageAccessEphemeral(subFrameDomain, topFrameDomain, frameID, webPageID, StorageAccessPromptWasShown::Yes, scope, WTFMove(completionHandler));
else
completionHandler({ StorageAccessWasGranted::No, StorageAccessPromptWasShown::Yes, scope, topFrameDomain, subFrameDomain });
};
m_networkSession->networkProcess().parentProcessConnection()->sendWithAsyncReply(Messages::NetworkProcessProxy::RequestStorageAccessConfirm(webPageProxyID, frameID, subFrameDomain, topFrameDomain), WTFMove(requestConfirmationCompletionHandler));
}
void WebResourceLoadStatisticsStore::requestStorageAccessUnderOpener(RegistrableDomain&& domainInNeedOfStorageAccess, PageIdentifier openerPageID, RegistrableDomain&& openerDomain)
{
ASSERT(RunLoop::isMain());
if (isEphemeral())
return requestStorageAccessUnderOpenerEphemeral(WTFMove(domainInNeedOfStorageAccess), openerPageID, WTFMove(openerDomain));
// It is safe to move the strings to the background queue without isolated copy here because they are r-value references
// coming from IPC. Strings which are safe to move to other threads as long as nobody on this thread holds a reference
// to those strings.
postTask([this, domainInNeedOfStorageAccess = WTFMove(domainInNeedOfStorageAccess), openerPageID, openerDomain = WTFMove(openerDomain)]() mutable {
if (m_statisticsStore)
m_statisticsStore->requestStorageAccessUnderOpener(WTFMove(domainInNeedOfStorageAccess), openerPageID, WTFMove(openerDomain));
});
}
void WebResourceLoadStatisticsStore::requestStorageAccessUnderOpenerEphemeral(RegistrableDomain&& domainInNeedOfStorageAccess, PageIdentifier openerPageID, RegistrableDomain&& openerDomain)
{
ASSERT(isEphemeral());
if (m_networkSession) {
if (auto* storageSession = m_networkSession->networkStorageSession())
storageSession->grantStorageAccess(WTFMove(domainInNeedOfStorageAccess), WTFMove(openerDomain), std::nullopt, openerPageID);
}
}
void WebResourceLoadStatisticsStore::grantStorageAccess(const RegistrableDomain& subFrameDomain, const RegistrableDomain& topFrameDomain, FrameIdentifier frameID, PageIdentifier pageID, StorageAccessPromptWasShown promptWasShown, StorageAccessScope scope, CompletionHandler<void(RequestStorageAccessResult)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, subFrameDomain = subFrameDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy(), frameID, pageID, promptWasShown, scope, completionHandler = WTFMove(completionHandler)]() mutable {
if (!m_statisticsStore) {
postTaskReply([subFrameDomain = subFrameDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy(), promptWasShown, scope, completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler({ StorageAccessWasGranted::No, promptWasShown, scope, topFrameDomain, subFrameDomain });
});
return;
}
m_statisticsStore->grantStorageAccess(WTFMove(subFrameDomain), WTFMove(topFrameDomain), frameID, pageID, promptWasShown, scope, [subFrameDomain = subFrameDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy(), promptWasShown, scope, completionHandler = WTFMove(completionHandler)](StorageAccessWasGranted wasGrantedAccess) mutable {
postTaskReply([subFrameDomain = subFrameDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy(), wasGrantedAccess, promptWasShown, scope, completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler({ wasGrantedAccess, promptWasShown, scope, topFrameDomain, subFrameDomain });
});
});
});
}
void WebResourceLoadStatisticsStore::grantStorageAccessEphemeral(const RegistrableDomain& subFrameDomain, const RegistrableDomain& topFrameDomain, FrameIdentifier frameID, PageIdentifier pageID, StorageAccessPromptWasShown promptWasShown, StorageAccessScope scope, CompletionHandler<void(RequestStorageAccessResult)>&& completionHandler)
{
ASSERT(isEphemeral());
if (m_networkSession) {
if (auto* storageSession = m_networkSession->networkStorageSession()) {
storageSession->grantStorageAccess(subFrameDomain, topFrameDomain, frameID, pageID);
completionHandler({ StorageAccessWasGranted::Yes, promptWasShown, scope, topFrameDomain, subFrameDomain });
return;
}
}
completionHandler({ StorageAccessWasGranted::No, promptWasShown, scope, topFrameDomain, subFrameDomain });
}
StorageAccessWasGranted WebResourceLoadStatisticsStore::grantStorageAccessInStorageSession(const RegistrableDomain& resourceDomain, const RegistrableDomain& firstPartyDomain, std::optional<FrameIdentifier> frameID, PageIdentifier pageID, StorageAccessScope scope)
{
ASSERT(RunLoop::isMain());
bool isStorageGranted = false;
if (m_networkSession) {
if (auto* storageSession = m_networkSession->networkStorageSession()) {
storageSession->grantStorageAccess(resourceDomain, firstPartyDomain, (scope == StorageAccessScope::PerFrame ? frameID : std::nullopt), pageID);
ASSERT(storageSession->hasStorageAccess(resourceDomain, firstPartyDomain, frameID, pageID));
isStorageGranted = true;
}
}
return isStorageGranted ? StorageAccessWasGranted::Yes : StorageAccessWasGranted::No;
}
void WebResourceLoadStatisticsStore::callGrantStorageAccessHandler(const RegistrableDomain& subFrameDomain, const RegistrableDomain& topFrameDomain, std::optional<FrameIdentifier> frameID, PageIdentifier pageID, StorageAccessScope scope, CompletionHandler<void(StorageAccessWasGranted)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
completionHandler(grantStorageAccessInStorageSession(subFrameDomain, topFrameDomain, frameID, pageID, scope));
}
void WebResourceLoadStatisticsStore::hasCookies(const RegistrableDomain& domain, CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (m_networkSession) {
if (auto* storageSession = m_networkSession->networkStorageSession()) {
storageSession->hasCookies(domain, WTFMove(completionHandler));
return;
}
}
completionHandler(false);
}
void WebResourceLoadStatisticsStore::setThirdPartyCookieBlockingMode(ThirdPartyCookieBlockingMode blockingMode)
{
ASSERT(RunLoop::isMain());
if (m_networkSession) {
if (auto* storageSession = m_networkSession->networkStorageSession())
storageSession->setThirdPartyCookieBlockingMode(blockingMode);
else
ASSERT_NOT_REACHED();
}
if (isEphemeral())
return;
postTask([this, blockingMode]() {
if (!m_statisticsStore)
return;
m_statisticsStore->setThirdPartyCookieBlockingMode(blockingMode);
});
}
void WebResourceLoadStatisticsStore::setSameSiteStrictEnforcementEnabled(SameSiteStrictEnforcementEnabled enabled)
{
ASSERT(RunLoop::isMain());
if (isEphemeral())
return;
postTask([this, enabled]() {
if (!m_statisticsStore)
return;
m_statisticsStore->setSameSiteStrictEnforcementEnabled(enabled);
});
}
void WebResourceLoadStatisticsStore::setFirstPartyWebsiteDataRemovalMode(FirstPartyWebsiteDataRemovalMode mode, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (isEphemeral()) {
completionHandler();
return;
}
postTask([this, mode, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore) {
m_statisticsStore->setFirstPartyWebsiteDataRemovalMode(mode);
if (mode == FirstPartyWebsiteDataRemovalMode::AllButCookiesReproTestingTimeout)
m_statisticsStore->setIsRunningTest(true);
}
postTaskReply([completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler();
});
});
}
void WebResourceLoadStatisticsStore::setStandaloneApplicationDomain(const RegistrableDomain& domain, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (isEphemeral() || domain.isEmpty()) {
completionHandler();
return;
}
RELEASE_LOG(ResourceLoadStatistics, "WebResourceLoadStatisticsStore::setStandaloneApplicationDomain() called with non-empty domain.");
postTask([this, domain = domain.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->setStandaloneApplicationDomain(WTFMove(domain));
postTaskReply([completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler();
});
});
}
#if ENABLE(APP_BOUND_DOMAINS)
void WebResourceLoadStatisticsStore::setAppBoundDomains(HashSet<RegistrableDomain>&& domains, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (isEphemeral() || domains.isEmpty()) {
completionHandler();
return;
}
auto domainsCopy = crossThreadCopy(domains);
if (m_networkSession) {
if (auto* storageSession = m_networkSession->networkStorageSession()) {
storageSession->setAppBoundDomains(WTFMove(domains));
storageSession->setThirdPartyCookieBlockingMode(ThirdPartyCookieBlockingMode::AllExceptBetweenAppBoundDomains);
}
}
postTask([this, domains = WTFMove(domainsCopy), completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore) {
m_statisticsStore->setAppBoundDomains(WTFMove(domains));
m_statisticsStore->setThirdPartyCookieBlockingMode(ThirdPartyCookieBlockingMode::AllExceptBetweenAppBoundDomains);
}
postTaskReply(WTFMove(completionHandler));
});
}
#endif
void WebResourceLoadStatisticsStore::didCreateNetworkProcess()
{
ASSERT(RunLoop::isMain());
postTask([this] {
if (!m_statisticsStore)
return;
m_statisticsStore->didCreateNetworkProcess();
});
}
void WebResourceLoadStatisticsStore::removeAllStorageAccess(CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (m_networkSession) {
if (auto* storageSession = m_networkSession->networkStorageSession())
storageSession->removeAllStorageAccess();
}
completionHandler();
}
void WebResourceLoadStatisticsStore::performDailyTasks()
{
ASSERT(RunLoop::isMain());
postTask([this] {
if (m_statisticsStore) {
m_statisticsStore->includeTodayAsOperatingDateIfNecessary();
if (is<ResourceLoadStatisticsDatabaseStore>(*m_statisticsStore))
downcast<ResourceLoadStatisticsDatabaseStore>(*m_statisticsStore).runIncrementalVacuumCommand();
}
});
}
void WebResourceLoadStatisticsStore::logFrameNavigation(const RegistrableDomain& targetDomain, const RegistrableDomain& topFrameDomain, const RegistrableDomain& sourceDomain, bool isRedirect, bool isMainFrame, Seconds delayAfterMainFrameDocumentLoad, bool wasPotentiallyInitiatedByUser)
{
ASSERT(RunLoop::isMain());
postTask([this, targetDomain = targetDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy(), sourceDomain = sourceDomain.isolatedCopy(), isRedirect, isMainFrame, delayAfterMainFrameDocumentLoad, wasPotentiallyInitiatedByUser] {
if (m_statisticsStore)
m_statisticsStore->logFrameNavigation(targetDomain, topFrameDomain, sourceDomain, isRedirect, isMainFrame, delayAfterMainFrameDocumentLoad, wasPotentiallyInitiatedByUser);
});
}
void WebResourceLoadStatisticsStore::logUserInteraction(const RegistrableDomain& domain, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
// User interactions need to be logged for ephemeral sessions to support the Storage Access API.
if (isEphemeral())
return logUserInteractionEphemeral(domain, WTFMove(completionHandler));
postTask([this, domain = domain.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
auto innerCompletionHandler = [completionHandler = WTFMove(completionHandler)]() mutable {
postTaskReply(WTFMove(completionHandler));
};
if (m_statisticsStore) {
m_statisticsStore->logUserInteraction(domain, WTFMove(innerCompletionHandler));
return;
}
innerCompletionHandler();
});
}
void WebResourceLoadStatisticsStore::logUserInteractionEphemeral(const RegistrableDomain& domain, CompletionHandler<void()>&& completionHandler)
{
ASSERT(isEphemeral());
m_domainsWithEphemeralUserInteraction.add(domain);
completionHandler();
}
void WebResourceLoadStatisticsStore::logCrossSiteLoadWithLinkDecoration(const RegistrableDomain& fromDomain, const RegistrableDomain& toDomain, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
ASSERT(fromDomain != toDomain);
postTask([this, fromDomain = fromDomain.isolatedCopy(), toDomain = toDomain.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->logCrossSiteLoadWithLinkDecoration(fromDomain, toDomain);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::clearUserInteraction(const RegistrableDomain& domain, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (isEphemeral())
return clearUserInteractionEphemeral(domain, WTFMove(completionHandler));
postTask([this, domain = domain.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
auto innerCompletionHandler = [completionHandler = WTFMove(completionHandler)]() mutable {
postTaskReply(WTFMove(completionHandler));
};
if (m_statisticsStore) {
m_statisticsStore->clearUserInteraction(domain, WTFMove(innerCompletionHandler));
return;
}
innerCompletionHandler();
});
}
void WebResourceLoadStatisticsStore::clearUserInteractionEphemeral(const RegistrableDomain& domain, CompletionHandler<void()>&& completionHandler)
{
ASSERT(isEphemeral());
m_domainsWithEphemeralUserInteraction.remove(domain);
completionHandler();
}
void WebResourceLoadStatisticsStore::hasHadUserInteraction(const RegistrableDomain& domain, CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (isEphemeral())
return hasHadUserInteractionEphemeral(domain, WTFMove(completionHandler));
postTask([this, domain = domain.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
bool hadUserInteraction = m_statisticsStore ? m_statisticsStore->hasHadUserInteraction(domain, OperatingDatesWindow::Long) : false;
postTaskReply([hadUserInteraction, completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(hadUserInteraction);
});
});
}
void WebResourceLoadStatisticsStore::hasHadUserInteractionEphemeral(const RegistrableDomain& domain, CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT(isEphemeral());
completionHandler(m_domainsWithEphemeralUserInteraction.contains(domain));
}
void WebResourceLoadStatisticsStore::setLastSeen(const RegistrableDomain& domain, Seconds seconds, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, domain = domain.isolatedCopy(), seconds, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->setLastSeen(domain, seconds);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::mergeStatisticForTesting(const RegistrableDomain& domain, const RegistrableDomain& topFrameDomain1, const RegistrableDomain& topFrameDomain2, Seconds lastSeen, bool hadUserInteraction, Seconds mostRecentUserInteraction, bool isGrandfathered, bool isPrevalent, bool isVeryPrevalent, unsigned dataRecordsRemoved, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, domain = domain.isolatedCopy(), topFrameDomain1 = topFrameDomain1.isolatedCopy(), topFrameDomain2 = topFrameDomain2.isolatedCopy(), lastSeen, hadUserInteraction, mostRecentUserInteraction, isGrandfathered, isPrevalent, isVeryPrevalent, dataRecordsRemoved, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore) {
ResourceLoadStatistics statistic(domain);
statistic.lastSeen = WallTime::fromRawSeconds(lastSeen.seconds());
statistic.hadUserInteraction = hadUserInteraction;
statistic.mostRecentUserInteractionTime = WallTime::fromRawSeconds(mostRecentUserInteraction.seconds());
statistic.grandfathered = isGrandfathered;
statistic.isPrevalentResource = isPrevalent;
statistic.isVeryPrevalentResource = isVeryPrevalent;
statistic.dataRecordsRemoved = dataRecordsRemoved;
HashSet<RegistrableDomain> topFrameDomains;
if (!topFrameDomain1.isEmpty())
topFrameDomains.add(topFrameDomain1);
if (!topFrameDomain2.isEmpty())
topFrameDomains.add(topFrameDomain2);
statistic.subframeUnderTopFrameDomains = WTFMove(topFrameDomains);
Vector<ResourceLoadStatistics> statistics;
statistics.append(WTFMove(statistic));
m_statisticsStore->mergeStatistics(WTFMove(statistics));
}
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::isRelationshipOnlyInDatabaseOnce(const RegistrableDomain& subDomain, const RegistrableDomain& topDomain, CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, subDomain = subDomain.isolatedCopy(), topDomain = topDomain.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
if (!m_statisticsStore || !is<ResourceLoadStatisticsDatabaseStore>(*m_statisticsStore)) {
completionHandler(false);
return;
}
bool isRelationshipOnlyInDatabaseOnce = downcast<ResourceLoadStatisticsDatabaseStore>(*m_statisticsStore).isCorrectSubStatisticsCount(subDomain, topDomain);
postTaskReply([isRelationshipOnlyInDatabaseOnce, completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(isRelationshipOnlyInDatabaseOnce);
});
});
}
void WebResourceLoadStatisticsStore::setPrevalentResource(const RegistrableDomain& domain, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, domain = domain.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->setPrevalentResource(domain);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::setVeryPrevalentResource(const RegistrableDomain& domain, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, domain = domain.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->setVeryPrevalentResource(domain);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::dumpResourceLoadStatistics(CompletionHandler<void(String)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, completionHandler = WTFMove(completionHandler)]() mutable {
auto innerCompletionHandler = [completionHandler = WTFMove(completionHandler)](const String& result) mutable {
postTaskReply([result = result.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(result);
});
};
if (!m_statisticsStore) {
innerCompletionHandler(emptyString());
return;
}
m_statisticsStore->dumpResourceLoadStatistics(WTFMove(innerCompletionHandler));
});
}
void WebResourceLoadStatisticsStore::isPrevalentResource(const RegistrableDomain& domain, CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (isEphemeral()) {
completionHandler(false);
return;
}
postTask([this, domain = domain.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
bool isPrevalentResource = m_statisticsStore ? m_statisticsStore->isPrevalentResource(domain) : false;
postTaskReply([isPrevalentResource, completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(isPrevalentResource);
});
});
}
void WebResourceLoadStatisticsStore::isVeryPrevalentResource(const RegistrableDomain& domain, CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, domain = domain.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
bool isVeryPrevalentResource = m_statisticsStore ? m_statisticsStore->isVeryPrevalentResource(domain) : false;
postTaskReply([isVeryPrevalentResource, completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(isVeryPrevalentResource);
});
});
}
void WebResourceLoadStatisticsStore::isRegisteredAsSubresourceUnder(const RegistrableDomain& subresourceDomain, const RegistrableDomain& topFrameDomain, CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, subresourceDomain = subresourceDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
bool isRegisteredAsSubresourceUnder = m_statisticsStore ? m_statisticsStore->isRegisteredAsSubresourceUnder(subresourceDomain, topFrameDomain)
: false;
postTaskReply([isRegisteredAsSubresourceUnder, completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(isRegisteredAsSubresourceUnder);
});
});
}
void WebResourceLoadStatisticsStore::isRegisteredAsSubFrameUnder(const RegistrableDomain& subFrameDomain, const RegistrableDomain& topFrameDomain, CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, subFrameDomain = subFrameDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
bool isRegisteredAsSubFrameUnder = m_statisticsStore ? m_statisticsStore->isRegisteredAsSubFrameUnder(subFrameDomain, topFrameDomain)
: false;
postTaskReply([isRegisteredAsSubFrameUnder, completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(isRegisteredAsSubFrameUnder);
});
});
}
void WebResourceLoadStatisticsStore::isRegisteredAsRedirectingTo(const RegistrableDomain& domainRedirectedFrom, const RegistrableDomain& domainRedirectedTo, CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, domainRedirectedFrom = domainRedirectedFrom.isolatedCopy(), domainRedirectedTo = domainRedirectedTo.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
bool isRegisteredAsRedirectingTo = m_statisticsStore ? m_statisticsStore->isRegisteredAsRedirectingTo(domainRedirectedFrom, domainRedirectedTo)
: false;
postTaskReply([isRegisteredAsRedirectingTo, completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(isRegisteredAsRedirectingTo);
});
});
}
void WebResourceLoadStatisticsStore::clearPrevalentResource(const RegistrableDomain& domain, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, domain = domain.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->clearPrevalentResource(domain);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::setGrandfathered(const RegistrableDomain& domain, bool value, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, domain = domain.isolatedCopy(), value, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->setGrandfathered(domain, value);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::isGrandfathered(const RegistrableDomain& domain, CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, completionHandler = WTFMove(completionHandler), domain = domain.isolatedCopy()]() mutable {
bool isGrandFathered = m_statisticsStore ? m_statisticsStore->isGrandfathered(domain)
: false;
postTaskReply([isGrandFathered, completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(isGrandFathered);
});
});
}
void WebResourceLoadStatisticsStore::setSubframeUnderTopFrameDomain(const RegistrableDomain& subFrameDomain, const RegistrableDomain& topFrameDomain, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, completionHandler = WTFMove(completionHandler), subFrameDomain = subFrameDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy()]() mutable {
if (m_statisticsStore)
m_statisticsStore->setSubframeUnderTopFrameDomain(subFrameDomain, topFrameDomain);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::setSubresourceUnderTopFrameDomain(const RegistrableDomain& subresourceDomain, const RegistrableDomain& topFrameDomain, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, completionHandler = WTFMove(completionHandler), subresourceDomain = subresourceDomain.isolatedCopy(), topFrameDomain = topFrameDomain.isolatedCopy()]() mutable {
if (m_statisticsStore)
m_statisticsStore->setSubresourceUnderTopFrameDomain(subresourceDomain, topFrameDomain);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::setSubresourceUniqueRedirectTo(const RegistrableDomain& subresourceDomain, const RegistrableDomain& domainRedirectedTo, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, completionHandler = WTFMove(completionHandler), subresourceDomain = subresourceDomain.isolatedCopy(), domainRedirectedTo = domainRedirectedTo.isolatedCopy()]() mutable {
if (m_statisticsStore)
m_statisticsStore->setSubresourceUniqueRedirectTo(subresourceDomain, domainRedirectedTo);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::setSubresourceUniqueRedirectFrom(const RegistrableDomain& subresourceDomain, const RegistrableDomain& domainRedirectedFrom, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, completionHandler = WTFMove(completionHandler), subresourceDomain = subresourceDomain.isolatedCopy(), domainRedirectedFrom = domainRedirectedFrom.isolatedCopy()]() mutable {
if (m_statisticsStore)
m_statisticsStore->setSubresourceUniqueRedirectFrom(subresourceDomain, domainRedirectedFrom);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::setTopFrameUniqueRedirectTo(const RegistrableDomain& topFrameDomain, const RegistrableDomain& domainRedirectedTo, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, completionHandler = WTFMove(completionHandler), topFrameDomain = topFrameDomain.isolatedCopy(), domainRedirectedTo = domainRedirectedTo.isolatedCopy()]() mutable {
if (m_statisticsStore)
m_statisticsStore->setTopFrameUniqueRedirectTo(topFrameDomain, domainRedirectedTo);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::setTopFrameUniqueRedirectFrom(const RegistrableDomain& topFrameDomain, const RegistrableDomain& domainRedirectedFrom, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, completionHandler = WTFMove(completionHandler), topFrameDomain = topFrameDomain.isolatedCopy(), domainRedirectedFrom = domainRedirectedFrom.isolatedCopy()]() mutable {
if (m_statisticsStore)
m_statisticsStore->setTopFrameUniqueRedirectFrom(topFrameDomain, domainRedirectedFrom);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::scheduleCookieBlockingUpdate(CompletionHandler<void()>&& completionHandler)
{
// Helper function used by testing system. Should only be called from the main thread.
ASSERT(RunLoop::isMain());
postTask([this, completionHandler = WTFMove(completionHandler)]() mutable {
if (!m_statisticsStore) {
postTaskReply(WTFMove(completionHandler));
return;
}
m_statisticsStore->updateCookieBlocking([completionHandler = WTFMove(completionHandler)]() mutable {
postTaskReply(WTFMove(completionHandler));
});
});
}
void WebResourceLoadStatisticsStore::scheduleClearInMemoryAndPersistent(ShouldGrandfatherStatistics shouldGrandfather, CompletionHandler<void()>&& completionHandler)
{
if (isEphemeral())
return clearInMemoryEphemeral(WTFMove(completionHandler));
ASSERT(RunLoop::isMain());
postTask([this, protectedThis = Ref { *this }, shouldGrandfather, completionHandler = WTFMove(completionHandler)]() mutable {
if (!m_statisticsStore) {
if (shouldGrandfather == ShouldGrandfatherStatistics::Yes)
RELEASE_LOG(ResourceLoadStatistics, "WebResourceLoadStatisticsStore::scheduleClearInMemoryAndPersistent Before being cleared, m_statisticsStore is null when trying to grandfather data.");
postTaskReply(WTFMove(completionHandler));
return;
}
auto callbackAggregator = CallbackAggregator::create([completionHandler = WTFMove(completionHandler)] () mutable {
postTaskReply(WTFMove(completionHandler));
});
m_statisticsStore->clear([this, protectedThis, shouldGrandfather, callbackAggregator] () mutable {
if (shouldGrandfather == ShouldGrandfatherStatistics::Yes) {
if (m_statisticsStore) {
m_statisticsStore->grandfatherExistingWebsiteData([callbackAggregator = WTFMove(callbackAggregator)]() mutable { });
if (is<ResourceLoadStatisticsDatabaseStore>(*m_statisticsStore))
downcast<ResourceLoadStatisticsDatabaseStore>(*m_statisticsStore).setIsNewResourceLoadStatisticsDatabaseFile(true);
} else
RELEASE_LOG(ResourceLoadStatistics, "WebResourceLoadStatisticsStore::scheduleClearInMemoryAndPersistent After being cleared, m_statisticsStore is null when trying to grandfather data.");
}
});
m_statisticsStore->cancelPendingStatisticsProcessingRequest();
});
}
void WebResourceLoadStatisticsStore::scheduleClearInMemoryAndPersistent(WallTime modifiedSince, ShouldGrandfatherStatistics shouldGrandfather, CompletionHandler<void()>&& callback)
{
ASSERT(RunLoop::isMain());
// For now, be conservative and clear everything regardless of modifiedSince.
UNUSED_PARAM(modifiedSince);
scheduleClearInMemoryAndPersistent(shouldGrandfather, WTFMove(callback));
}
void WebResourceLoadStatisticsStore::clearInMemoryEphemeral(CompletionHandler<void()>&& completionHandler)
{
m_domainsWithEphemeralUserInteraction.clear();
if (auto* storageSession = m_networkSession->networkStorageSession())
storageSession->removeAllStorageAccess();
completionHandler();
}
void WebResourceLoadStatisticsStore::domainIDExistsInDatabase(int domainID, CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, domainID, completionHandler = WTFMove(completionHandler)]() mutable {
if (!m_statisticsStore || !is<ResourceLoadStatisticsDatabaseStore>(*m_statisticsStore)) {
completionHandler(false);
return;
}
auto& databaseStore = downcast<ResourceLoadStatisticsDatabaseStore>(*m_statisticsStore);
bool domainIDExists = databaseStore.domainIDExistsInDatabase(domainID);
postTaskReply([domainIDExists, completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(domainIDExists);
});
});
}
void WebResourceLoadStatisticsStore::setTimeToLiveUserInteraction(Seconds seconds, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, seconds, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->setTimeToLiveUserInteraction(seconds);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::setMinimumTimeBetweenDataRecordsRemoval(Seconds seconds, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, seconds, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->setMinimumTimeBetweenDataRecordsRemoval(seconds);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::setGrandfatheringTime(Seconds seconds, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, seconds, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->setGrandfatheringTime(seconds);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::setCacheMaxAgeCap(Seconds seconds, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
ASSERT(seconds >= 0_s);
if (m_networkSession) {
if (auto* storageSession = m_networkSession->networkStorageSession())
storageSession->setCacheMaxAgeCapForPrevalentResources(seconds);
}
completionHandler();
}
bool WebResourceLoadStatisticsStore::needsUserInteractionQuirk(const RegistrableDomain& domain) const
{
static NeverDestroyed<HashSet<RegistrableDomain>> quirks = [] {
HashSet<RegistrableDomain> set;
set.add(RegistrableDomain::uncheckedCreateFromRegistrableDomainString("kinja.com"_s));
set.add(RegistrableDomain::uncheckedCreateFromRegistrableDomainString("youtube.com"_s));
return set;
}();
return quirks.get().contains(domain);
}
void WebResourceLoadStatisticsStore::callUpdatePrevalentDomainsToBlockCookiesForHandler(const RegistrableDomainsToBlockCookiesFor& domainsToBlock, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (m_networkSession) {
if (auto* storageSession = m_networkSession->networkStorageSession()) {
storageSession->setPrevalentDomainsToBlockAndDeleteCookiesFor(domainsToBlock.domainsToBlockAndDeleteCookiesFor);
storageSession->setPrevalentDomainsToBlockButKeepCookiesFor(domainsToBlock.domainsToBlockButKeepCookiesFor);
storageSession->setDomainsWithUserInteractionAsFirstParty(domainsToBlock.domainsWithUserInteractionAsFirstParty);
}
HashSet<RegistrableDomain> domainsWithUserInteractionQuirk;
for (auto& domain : domainsToBlock.domainsWithUserInteractionAsFirstParty) {
if (needsUserInteractionQuirk(domain))
domainsWithUserInteractionQuirk.add(domain);
}
if (m_domainsWithUserInteractionQuirk != domainsWithUserInteractionQuirk) {
m_domainsWithUserInteractionQuirk = domainsWithUserInteractionQuirk;
m_networkSession->networkProcess().parentProcessConnection()->send(Messages::NetworkProcessProxy::SetDomainsWithUserInteraction(domainsWithUserInteractionQuirk), 0);
}
HashMap<TopFrameDomain, SubResourceDomain> domainsWithStorageAccessQuirk;
for (auto& firstPartyDomain : domainsToBlock.domainsWithStorageAccess.keys()) {
auto requestingDomain = domainsToBlock.domainsWithStorageAccess.get(firstPartyDomain);
if (NetworkStorageSession::loginDomainMatchesRequestingDomain(firstPartyDomain, requestingDomain))
domainsWithStorageAccessQuirk.add(firstPartyDomain, requestingDomain);
}
if (m_domainsWithCrossPageStorageAccessQuirk != domainsWithStorageAccessQuirk) {
if (m_networkSession) {
if (auto* storageSession = m_networkSession->networkStorageSession())
storageSession->setDomainsWithCrossPageStorageAccess(domainsWithStorageAccessQuirk);
m_networkSession->networkProcess().parentProcessConnection()->sendWithAsyncReply(Messages::NetworkProcessProxy::SetDomainsWithCrossPageStorageAccess(domainsWithStorageAccessQuirk), [this, domainsWithStorageAccessQuirk] () mutable {
m_domainsWithCrossPageStorageAccessQuirk = domainsWithStorageAccessQuirk;
});
}
}
}
completionHandler();
}
void WebResourceLoadStatisticsStore::setMaxStatisticsEntries(size_t maximumEntryCount, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, maximumEntryCount, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->setMaxStatisticsEntries(maximumEntryCount);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::setPruneEntriesDownTo(size_t pruneTargetCount, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, pruneTargetCount, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->setPruneEntriesDownTo(pruneTargetCount);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::resetParametersToDefaultValues(CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (isEphemeral()) {
completionHandler();
return;
}
#if ENABLE(APP_BOUND_DOMAINS)
if (m_networkSession) {
if (auto* storageSession = m_networkSession->networkStorageSession())
storageSession->resetAppBoundDomains();
}
#endif
postTask([this, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->resetParametersToDefaultValues();
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::logTestingEvent(const String& event)
{
ASSERT(RunLoop::isMain());
if (m_networkSession && m_networkSession->enableResourceLoadStatisticsLogTestingEvent())
m_networkSession->networkProcess().parentProcessConnection()->send(Messages::NetworkProcessProxy::LogTestingEvent(m_networkSession->sessionID(), event), 0);
}
void WebResourceLoadStatisticsStore::notifyResourceLoadStatisticsProcessed()
{
ASSERT(RunLoop::isMain());
if (m_networkSession)
m_networkSession->notifyResourceLoadStatisticsProcessed();
}
NetworkSession* WebResourceLoadStatisticsStore::networkSession()
{
ASSERT(RunLoop::isMain());
return m_networkSession.get();
}
void WebResourceLoadStatisticsStore::invalidateAndCancel()
{
ASSERT(RunLoop::isMain());
m_networkSession = nullptr;
}
void WebResourceLoadStatisticsStore::removeDataForDomain(RegistrableDomain domain, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, domain = WTFMove(domain), completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->removeDataForDomain(domain);
postTaskReply(WTFMove(completionHandler));
});
}
void WebResourceLoadStatisticsStore::registrableDomains(CompletionHandler<void(Vector<RegistrableDomain>&&)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, completionHandler = WTFMove(completionHandler)]() mutable {
auto domains = m_statisticsStore ? m_statisticsStore->allDomains() : Vector<RegistrableDomain>();
postTaskReply([domains = crossThreadCopy(WTFMove(domains)), completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(WTFMove(domains));
});
});
}
void WebResourceLoadStatisticsStore::deleteAndRestrictWebsiteDataForRegistrableDomains(OptionSet<WebsiteDataType> dataTypes, RegistrableDomainsToDeleteOrRestrictWebsiteDataFor&& domainsToDeleteAndRestrictWebsiteDataFor, bool shouldNotifyPage, CompletionHandler<void(const HashSet<RegistrableDomain>&)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (m_networkSession) {
m_networkSession->deleteAndRestrictWebsiteDataForRegistrableDomains(dataTypes, WTFMove(domainsToDeleteAndRestrictWebsiteDataFor), shouldNotifyPage, WTFMove(completionHandler));
return;
}
completionHandler({ });
}
void WebResourceLoadStatisticsStore::registrableDomainsWithWebsiteData(OptionSet<WebsiteDataType> dataTypes, bool shouldNotifyPage, CompletionHandler<void(HashSet<RegistrableDomain>&&)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
if (m_networkSession) {
m_networkSession->registrableDomainsWithWebsiteData(dataTypes, shouldNotifyPage, WTFMove(completionHandler));
return;
}
completionHandler({ });
}
void WebResourceLoadStatisticsStore::sendDiagnosticMessageWithValue(const String& message, const String& description, unsigned value, unsigned sigDigits, WebCore::ShouldSample shouldSample) const
{
ASSERT(RunLoop::isMain());
if (m_networkSession)
const_cast<WebResourceLoadStatisticsStore*>(this)->networkSession()->logDiagnosticMessageWithValue(message, description, value, sigDigits, shouldSample);
}
void WebResourceLoadStatisticsStore::aggregatedThirdPartyData(CompletionHandler<void(Vector<WebResourceLoadStatisticsStore::ThirdPartyData>&&)>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, completionHandler = WTFMove(completionHandler)]() mutable {
if (!m_statisticsStore) {
postTaskReply([completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler({ });
});
return;
}
auto thirdPartyData = m_statisticsStore->aggregatedThirdPartyData();
postTaskReply([thirdPartyData = WTFMove(thirdPartyData), completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(WTFMove(thirdPartyData));
});
});
}
void WebResourceLoadStatisticsStore::suspend(CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
sharedStatisticsQueue()->suspend(ResourceLoadStatisticsDatabaseStore::interruptAllDatabases, WTFMove(completionHandler));
}
void WebResourceLoadStatisticsStore::resume()
{
ASSERT(RunLoop::isMain());
sharedStatisticsQueue()->resume();
}
void WebResourceLoadStatisticsStore::insertExpiredStatisticForTesting(const RegistrableDomain& domain, unsigned numberOfOperatingDaysPassed, bool hadUserInteraction, bool isScheduledForAllButCookieDataRemoval, bool isPrevalent, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
postTask([this, domain = domain.isolatedCopy(), numberOfOperatingDaysPassed, hadUserInteraction, isScheduledForAllButCookieDataRemoval, isPrevalent, completionHandler = WTFMove(completionHandler)]() mutable {
if (m_statisticsStore)
m_statisticsStore->insertExpiredStatisticForTesting(WTFMove(domain), numberOfOperatingDaysPassed, hadUserInteraction, isScheduledForAllButCookieDataRemoval, isPrevalent);
postTaskReply(WTFMove(completionHandler));
});
}
String WebResourceLoadStatisticsStore::ThirdPartyDataForSpecificFirstParty::toString() const
{
return makeString("Has been granted storage access under ", firstPartyDomain.string(), ": ", storageAccessGranted ? '1' : '0', "; Has been seen under ", firstPartyDomain.string(), " in the last 24 hours: ", WallTime::now().secondsSinceEpoch() - timeLastUpdated < 24_h ? '1' : '0');
}
void WebResourceLoadStatisticsStore::ThirdPartyDataForSpecificFirstParty::encode(IPC::Encoder& encoder) const
{
encoder << firstPartyDomain;
encoder << storageAccessGranted;
encoder << timeLastUpdated;
}
auto WebResourceLoadStatisticsStore::ThirdPartyDataForSpecificFirstParty::decode(IPC::Decoder& decoder) -> std::optional<ThirdPartyDataForSpecificFirstParty>
{
std::optional<WebCore::RegistrableDomain> decodedDomain;
decoder >> decodedDomain;
if (!decodedDomain)
return std::nullopt;
std::optional<bool> decodedStorageAccess;
decoder >> decodedStorageAccess;
if (!decodedStorageAccess)
return std::nullopt;
std::optional<Seconds> decodedTimeLastUpdated;
decoder >> decodedTimeLastUpdated;
if (!decodedTimeLastUpdated)
return std::nullopt;
return {{ WTFMove(*decodedDomain), WTFMove(*decodedStorageAccess), WTFMove(*decodedTimeLastUpdated) }};
}
bool WebResourceLoadStatisticsStore::ThirdPartyDataForSpecificFirstParty::operator==(const ThirdPartyDataForSpecificFirstParty& other) const
{
return firstPartyDomain == other.firstPartyDomain && storageAccessGranted == other.storageAccessGranted;
}
String WebResourceLoadStatisticsStore::ThirdPartyData::toString() const
{
StringBuilder stringBuilder;
stringBuilder.append("Third Party Registrable Domain: ", thirdPartyDomain.string(), "\n {");
for (auto firstParty : underFirstParties)
stringBuilder.append("{ ", firstParty.toString(), " },");
stringBuilder.append('}');
return stringBuilder.toString();
}
void WebResourceLoadStatisticsStore::ThirdPartyData::encode(IPC::Encoder& encoder) const
{
encoder << thirdPartyDomain;
encoder << underFirstParties;
}
auto WebResourceLoadStatisticsStore::ThirdPartyData::decode(IPC::Decoder& decoder) -> std::optional<ThirdPartyData>
{
std::optional<WebCore::RegistrableDomain> decodedDomain;
decoder >> decodedDomain;
if (!decodedDomain)
return std::nullopt;
std::optional<Vector<ThirdPartyDataForSpecificFirstParty>> decodedFirstParties;
decoder >> decodedFirstParties;
if (!decodedFirstParties)
return std::nullopt;
return {{ WTFMove(*decodedDomain), WTFMove(*decodedFirstParties) }};
}
bool WebResourceLoadStatisticsStore::ThirdPartyData::operator<(const ThirdPartyData &other) const
{
return underFirstParties.size() < other.underFirstParties.size();
}
} // namespace WebKit
#endif