blob: ee8c25e8ab62ed4cbdce967f3966d6ba33ead0e1 [file] [log] [blame]
/*
* Copyright (C) 2019-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 "DocumentStorageAccess.h"
#if ENABLE(INTELLIGENT_TRACKING_PREVENTION)
#include "Chrome.h"
#include "ChromeClient.h"
#include "Document.h"
#include "EventLoop.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "JSDOMPromiseDeferred.h"
#include "NetworkStorageSession.h"
#include "Page.h"
#include "Quirks.h"
#include "RegistrableDomain.h"
#include "SecurityOrigin.h"
#include "Settings.h"
#include "UserGestureIndicator.h"
namespace WebCore {
DocumentStorageAccess::DocumentStorageAccess(Document& document)
: m_document(document)
{
}
DocumentStorageAccess::~DocumentStorageAccess() = default;
DocumentStorageAccess* DocumentStorageAccess::from(Document& document)
{
auto* supplement = static_cast<DocumentStorageAccess*>(Supplement<Document>::from(&document, supplementName()));
if (!supplement) {
auto newSupplement = makeUnique<DocumentStorageAccess>(document);
supplement = newSupplement.get();
provideTo(&document, supplementName(), WTFMove(newSupplement));
}
return supplement;
}
const char* DocumentStorageAccess::supplementName()
{
return "DocumentStorageAccess";
}
void DocumentStorageAccess::hasStorageAccess(Document& document, Ref<DeferredPromise>&& promise)
{
DocumentStorageAccess::from(document)->hasStorageAccess(WTFMove(promise));
}
std::optional<bool> DocumentStorageAccess::hasStorageAccessQuickCheck()
{
ASSERT(m_document.settings().storageAccessAPIEnabled());
auto* frame = m_document.frame();
if (frame && hasFrameSpecificStorageAccess())
return true;
auto& securityOrigin = m_document.securityOrigin();
if (!frame || securityOrigin.isUnique())
return false;
if (frame->isMainFrame())
return true;
if (securityOrigin.equal(&m_document.topOrigin()))
return true;
auto* page = frame->page();
if (!page)
return false;
return std::nullopt;
}
void DocumentStorageAccess::hasStorageAccess(Ref<DeferredPromise>&& promise)
{
ASSERT(m_document.settings().storageAccessAPIEnabled());
auto quickCheckResult = hasStorageAccessQuickCheck();
if (quickCheckResult) {
promise->resolve<IDLBoolean>(*quickCheckResult);
return;
}
// The existence of a frame and page has been checked in requestStorageAccessQuickCheck().
auto* frame = m_document.frame();
if (!frame) {
ASSERT_NOT_REACHED();
promise->resolve<IDLBoolean>(false);
return;
}
auto* page = frame->page();
if (!page) {
ASSERT_NOT_REACHED();
promise->resolve<IDLBoolean>(false);
return;
}
page->chrome().client().hasStorageAccess(RegistrableDomain::uncheckedCreateFromHost(m_document.securityOrigin().host()), RegistrableDomain::uncheckedCreateFromHost(m_document.topOrigin().host()), *frame, [weakThis = WeakPtr { *this }, promise = WTFMove(promise)] (bool hasAccess) {
if (!weakThis)
return;
promise->resolve<IDLBoolean>(hasAccess);
});
}
bool DocumentStorageAccess::hasStorageAccessForDocumentQuirk(Document& document)
{
auto quickCheckResult = DocumentStorageAccess::from(document)->hasStorageAccessQuickCheck();
if (quickCheckResult)
return *quickCheckResult;
return false;
}
void DocumentStorageAccess::requestStorageAccess(Document& document, Ref<DeferredPromise>&& promise)
{
DocumentStorageAccess::from(document)->requestStorageAccess(WTFMove(promise));
}
std::optional<StorageAccessQuickResult> DocumentStorageAccess::requestStorageAccessQuickCheck()
{
ASSERT(m_document.settings().storageAccessAPIEnabled());
auto* frame = m_document.frame();
if (frame && hasFrameSpecificStorageAccess())
return StorageAccessQuickResult::Grant;
auto& securityOrigin = m_document.securityOrigin();
if (!frame || securityOrigin.isUnique() || !isAllowedToRequestStorageAccess())
return StorageAccessQuickResult::Reject;
if (frame->isMainFrame())
return StorageAccessQuickResult::Grant;
if (securityOrigin.equal(&m_document.topOrigin()))
return StorageAccessQuickResult::Grant;
// If there is a sandbox, it has to allow the storage access API to be called.
if (m_document.sandboxFlags() != SandboxNone && m_document.isSandboxed(SandboxStorageAccessByUserActivation))
return StorageAccessQuickResult::Reject;
if (!UserGestureIndicator::processingUserGesture())
return StorageAccessQuickResult::Reject;
return std::nullopt;
}
void DocumentStorageAccess::requestStorageAccess(Ref<DeferredPromise>&& promise)
{
ASSERT(m_document.settings().storageAccessAPIEnabled());
auto quickCheckResult = requestStorageAccessQuickCheck();
if (quickCheckResult) {
*quickCheckResult == StorageAccessQuickResult::Grant ? promise->resolve() : promise->reject();
return;
}
// The existence of a frame and page has been checked in requestStorageAccessQuickCheck().
auto* frame = m_document.frame();
if (!frame) {
ASSERT_NOT_REACHED();
promise->reject();
return;
}
auto* page = frame->page();
if (!page) {
ASSERT_NOT_REACHED();
promise->reject();
return;
}
if (!page->settings().storageAccessAPIPerPageScopeEnabled())
m_storageAccessScope = StorageAccessScope::PerFrame;
page->chrome().client().requestStorageAccess(RegistrableDomain::uncheckedCreateFromHost(m_document.securityOrigin().host()), RegistrableDomain::uncheckedCreateFromHost(m_document.topOrigin().host()), *frame, m_storageAccessScope, [this, weakThis = WeakPtr { *this }, promise = WTFMove(promise)] (RequestStorageAccessResult result) mutable {
if (!weakThis)
return;
// Consume the user gesture only if the user explicitly denied access.
bool shouldPreserveUserGesture = result.wasGranted == StorageAccessWasGranted::Yes || result.promptWasShown == StorageAccessPromptWasShown::No;
if (shouldPreserveUserGesture) {
m_document.eventLoop().queueMicrotask([this, weakThis] {
if (weakThis)
enableTemporaryTimeUserGesture();
});
}
if (result.wasGranted == StorageAccessWasGranted::Yes)
promise->resolve();
else {
if (result.promptWasShown == StorageAccessPromptWasShown::Yes)
setWasExplicitlyDeniedFrameSpecificStorageAccess();
promise->reject();
}
if (shouldPreserveUserGesture) {
m_document.eventLoop().queueMicrotask([this, weakThis] {
if (weakThis)
consumeTemporaryTimeUserGesture();
});
}
});
}
void DocumentStorageAccess::requestStorageAccessForDocumentQuirk(Document& document, CompletionHandler<void(StorageAccessWasGranted)>&& completionHandler)
{
DocumentStorageAccess::from(document)->requestStorageAccessForDocumentQuirk(WTFMove(completionHandler));
}
void DocumentStorageAccess::requestStorageAccessForDocumentQuirk(CompletionHandler<void(StorageAccessWasGranted)>&& completionHandler)
{
auto quickCheckResult = requestStorageAccessQuickCheck();
if (quickCheckResult) {
*quickCheckResult == StorageAccessQuickResult::Grant ? completionHandler(StorageAccessWasGranted::Yes) : completionHandler(StorageAccessWasGranted::No);
return;
}
requestStorageAccessQuirk(RegistrableDomain::uncheckedCreateFromHost(m_document.securityOrigin().host()), WTFMove(completionHandler));
}
void DocumentStorageAccess::requestStorageAccessForNonDocumentQuirk(Document& hostingDocument, RegistrableDomain&& requestingDomain, CompletionHandler<void(StorageAccessWasGranted)>&& completionHandler)
{
DocumentStorageAccess::from(hostingDocument)->requestStorageAccessForNonDocumentQuirk(WTFMove(requestingDomain), WTFMove(completionHandler));
}
void DocumentStorageAccess::requestStorageAccessForNonDocumentQuirk(RegistrableDomain&& requestingDomain, CompletionHandler<void(StorageAccessWasGranted)>&& completionHandler)
{
if (!m_document.frame() || !m_document.frame()->page() || !isAllowedToRequestStorageAccess()) {
completionHandler(StorageAccessWasGranted::No);
return;
}
requestStorageAccessQuirk(WTFMove(requestingDomain), WTFMove(completionHandler));
}
void DocumentStorageAccess::requestStorageAccessQuirk(RegistrableDomain&& requestingDomain, CompletionHandler<void(StorageAccessWasGranted)>&& completionHandler)
{
ASSERT(m_document.settings().storageAccessAPIEnabled());
RELEASE_ASSERT(m_document.frame() && m_document.frame()->page());
auto topFrameDomain = RegistrableDomain(m_document.topDocument().url());
m_document.frame()->page()->chrome().client().requestStorageAccess(WTFMove(requestingDomain), WTFMove(topFrameDomain), *m_document.frame(), m_storageAccessScope, [this, weakThis = WeakPtr { *this }, completionHandler = WTFMove(completionHandler)] (RequestStorageAccessResult result) mutable {
if (!weakThis)
return;
// Consume the user gesture only if the user explicitly denied access.
bool shouldPreserveUserGesture = result.wasGranted == StorageAccessWasGranted::Yes || result.promptWasShown == StorageAccessPromptWasShown::No;
if (shouldPreserveUserGesture) {
m_document.eventLoop().queueMicrotask([this, weakThis] {
if (weakThis)
enableTemporaryTimeUserGesture();
});
}
if (result.wasGranted == StorageAccessWasGranted::Yes)
completionHandler(StorageAccessWasGranted::Yes);
else {
if (result.promptWasShown == StorageAccessPromptWasShown::Yes)
setWasExplicitlyDeniedFrameSpecificStorageAccess();
completionHandler(StorageAccessWasGranted::No);
}
if (shouldPreserveUserGesture) {
m_document.eventLoop().queueMicrotask([this, weakThis] {
if (weakThis)
consumeTemporaryTimeUserGesture();
});
}
});
}
void DocumentStorageAccess::enableTemporaryTimeUserGesture()
{
m_temporaryUserGesture = makeUnique<UserGestureIndicator>(ProcessingUserGesture, &m_document);
}
void DocumentStorageAccess::consumeTemporaryTimeUserGesture()
{
m_temporaryUserGesture = nullptr;
}
bool DocumentStorageAccess::hasFrameSpecificStorageAccess() const
{
auto* frame = m_document.frame();
return frame && frame->loader().client().hasFrameSpecificStorageAccess();
}
} // namespace WebCore
#endif // ENABLE(INTELLIGENT_TRACKING_PREVENTION)