blob: 81efb10d5e76edb6f6d383530051d29c388eafdb [file] [log] [blame]
/*
* Copyright (C) 2019 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(RESOURCE_LOAD_STATISTICS)
#include "Chrome.h"
#include "ChromeClient.h"
#include "Document.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "JSDOMPromiseDeferred.h"
#include "Microtasks.h"
#include "Page.h"
#include "RegistrableDomain.h"
#include "SecurityOrigin.h"
#include "Settings.h"
#include "UserGestureIndicator.h"
namespace WebCore {
DocumentStorageAccess* DocumentStorageAccess::from(Document& document)
{
auto* supplement = static_cast<DocumentStorageAccess*>(Supplement<Document>::from(&document, supplementName()));
if (!supplement) {
auto newSupplement = std::make_unique<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));
}
void DocumentStorageAccess::requestStorageAccess(Document& document, Ref<DeferredPromise>&& promise)
{
DocumentStorageAccess::from(document)->requestStorageAccess(WTFMove(promise));
}
void DocumentStorageAccess::hasStorageAccess(Ref<DeferredPromise>&& promise)
{
ASSERT(m_document.settings().storageAccessAPIEnabled());
if (m_document.frame() && hasFrameSpecificStorageAccess()) {
promise->resolve<IDLBoolean>(true);
return;
}
if (!m_document.frame() || m_document.securityOrigin().isUnique()) {
promise->resolve<IDLBoolean>(false);
return;
}
if (m_document.frame()->isMainFrame()) {
promise->resolve<IDLBoolean>(true);
return;
}
auto& securityOrigin = m_document.securityOrigin();
auto& topSecurityOrigin = m_document.topDocument().securityOrigin();
if (securityOrigin.equal(&topSecurityOrigin)) {
promise->resolve<IDLBoolean>(true);
return;
}
auto frameID = m_document.frame()->loader().client().frameID();
auto pageID = m_document.frame()->loader().client().pageID();
if (!frameID || !pageID) {
promise->reject();
return;
}
if (Page* page = m_document.page()) {
auto subFrameDomain = RegistrableDomain::uncheckedCreateFromHost(securityOrigin.host());
auto topFrameDomain = RegistrableDomain::uncheckedCreateFromHost(topSecurityOrigin.host());
page->chrome().client().hasStorageAccess(WTFMove(subFrameDomain), WTFMove(topFrameDomain), frameID.value(), pageID.value(), [weakThis = makeWeakPtr(*this), promise = WTFMove(promise)] (bool hasAccess) {
DocumentStorageAccess* document = weakThis.get();
if (!document)
return;
promise->resolve<IDLBoolean>(hasAccess);
});
return;
}
promise->reject();
}
void DocumentStorageAccess::requestStorageAccess(Ref<DeferredPromise>&& promise)
{
ASSERT(m_document.settings().storageAccessAPIEnabled());
if (m_document.frame() && hasFrameSpecificStorageAccess()) {
promise->resolve();
return;
}
if (!m_document.frame() || m_document.securityOrigin().isUnique() || !isAllowedToRequestFrameSpecificStorageAccess()) {
promise->reject();
return;
}
if (m_document.frame()->isMainFrame()) {
promise->resolve();
return;
}
auto& topDocument = m_document.topDocument();
auto& topSecurityOrigin = topDocument.securityOrigin();
auto& securityOrigin = m_document.securityOrigin();
if (securityOrigin.equal(&topSecurityOrigin)) {
promise->resolve();
return;
}
// 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)) {
promise->reject();
return;
}
// The iframe has to be a direct child of the top document.
if (&topDocument != m_document.parentDocument()) {
promise->reject();
return;
}
if (!UserGestureIndicator::processingUserGesture()) {
promise->reject();
return;
}
Page* page = m_document.page();
auto frameID = m_document.frame()->loader().client().frameID();
auto pageID = m_document.frame()->loader().client().pageID();
if (!page || !frameID || !pageID) {
promise->reject();
return;
}
auto subFrameDomain = RegistrableDomain::uncheckedCreateFromHost(securityOrigin.host());
auto topFrameDomain = RegistrableDomain::uncheckedCreateFromHost(topSecurityOrigin.host());
page->chrome().client().requestStorageAccess(WTFMove(subFrameDomain), WTFMove(topFrameDomain), frameID.value(), pageID.value(), [documentReference = makeWeakPtr(*this), promise = WTFMove(promise)] (StorageAccessWasGranted wasGranted, StorageAccessPromptWasShown promptWasShown) mutable {
DocumentStorageAccess* document = documentReference.get();
if (!document)
return;
// Consume the user gesture only if the user explicitly denied access.
bool shouldPreserveUserGesture = wasGranted == StorageAccessWasGranted::Yes || promptWasShown == StorageAccessPromptWasShown::No;
if (shouldPreserveUserGesture) {
MicrotaskQueue::mainThreadQueue().append(std::make_unique<VoidMicrotask>([documentReference = makeWeakPtr(*document)] () {
if (auto* document = documentReference.get())
document->enableTemporaryTimeUserGesture();
}));
}
if (wasGranted == StorageAccessWasGranted::Yes) {
document->setHasFrameSpecificStorageAccess(true);
promise->resolve();
} else {
if (promptWasShown == StorageAccessPromptWasShown::Yes)
document->setWasExplicitlyDeniedFrameSpecificStorageAccess();
promise->reject();
}
if (shouldPreserveUserGesture) {
MicrotaskQueue::mainThreadQueue().append(std::make_unique<VoidMicrotask>([documentReference = WTFMove(documentReference)] () {
if (auto* document = documentReference.get())
document->consumeTemporaryTimeUserGesture();
}));
}
});
}
void DocumentStorageAccess::enableTemporaryTimeUserGesture()
{
m_temporaryUserGesture = std::make_unique<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();
}
void DocumentStorageAccess::setHasFrameSpecificStorageAccess(bool value)
{
if (auto* frame = m_document.frame())
frame->loader().client().setHasFrameSpecificStorageAccess(value);
}
} // namespace WebCore
#endif // ENABLE(RESOURCE_LOAD_STATISTICS)