blob: d21ed5f8835876576f28d16e2c0daaf19f973a9c [file] [log] [blame]
/*
* Copyright (C) 2013 Google 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:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
* OWNER OR 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 "core/loader/FrameFetchContext.h"
#include <algorithm>
#include <memory>
#include "bindings/core/v8/ScriptController.h"
#include "bindings/core/v8/V8DOMActivityLogger.h"
#include "core/dom/Document.h"
#include "core/frame/Deprecation.h"
#include "core/frame/FrameConsole.h"
#include "core/frame/FrameHost.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalDOMWindow.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/LocalFrameClient.h"
#include "core/frame/Settings.h"
#include "core/frame/UseCounter.h"
#include "core/html/HTMLFrameOwnerElement.h"
#include "core/html/imports/HTMLImportsController.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/inspector/IdentifiersFactory.h"
#include "core/inspector/InspectorInstrumentation.h"
#include "core/inspector/InspectorNetworkAgent.h"
#include "core/inspector/InspectorTraceEvents.h"
#include "core/loader/DocumentLoader.h"
#include "core/loader/FrameLoader.h"
#include "core/loader/MixedContentChecker.h"
#include "core/loader/NetworkHintsInterface.h"
#include "core/loader/PingLoader.h"
#include "core/loader/ProgressTracker.h"
#include "core/loader/SubresourceFilter.h"
#include "core/loader/appcache/ApplicationCacheHost.h"
#include "core/loader/private/FrameClientHintsPreferencesContext.h"
#include "core/page/Page.h"
#include "core/paint/FirstMeaningfulPaintDetector.h"
#include "core/svg/graphics/SVGImageChromeClient.h"
#include "core/timing/DOMWindowPerformance.h"
#include "core/timing/Performance.h"
#include "platform/WebFrameScheduler.h"
#include "platform/exported/WrappedResourceRequest.h"
#include "platform/instrumentation/tracing/TracedValue.h"
#include "platform/loader/fetch/ClientHintsPreferences.h"
#include "platform/loader/fetch/FetchInitiatorTypeNames.h"
#include "platform/loader/fetch/Resource.h"
#include "platform/loader/fetch/ResourceLoadPriority.h"
#include "platform/loader/fetch/ResourceLoadingLog.h"
#include "platform/loader/fetch/ResourceTimingInfo.h"
#include "platform/loader/fetch/UniqueIdentifier.h"
#include "platform/mhtml/MHTMLArchive.h"
#include "platform/network/NetworkStateNotifier.h"
#include "platform/network/NetworkUtils.h"
#include "platform/weborigin/SchemeRegistry.h"
#include "platform/weborigin/SecurityPolicy.h"
#include "public/platform/WebCachePolicy.h"
#include "public/platform/WebInsecureRequestPolicy.h"
#include "public/platform/WebViewScheduler.h"
#include "public/platform/modules/serviceworker/WebServiceWorkerNetworkProvider.h"
#include "wtf/Vector.h"
namespace blink {
namespace {
void emitWarningForDocWriteScripts(const String& url, Document& document) {
String message =
"A Parser-blocking, cross site (i.e. different eTLD+1) script, " + url +
", is invoked via document.write. The network request for this script "
"MAY be blocked by the browser in this or a future page load due to poor "
"network connectivity. If blocked in this page load, it will be "
"confirmed in a subsequent console message."
"See https://www.chromestatus.com/feature/5718547946799104 "
"for more details.";
document.addConsoleMessage(
ConsoleMessage::create(JSMessageSource, WarningMessageLevel, message));
WTFLogAlways("%s", message.utf8().data());
}
bool isConnectionEffectively2G(WebEffectiveConnectionType effectiveType) {
switch (effectiveType) {
case WebEffectiveConnectionType::TypeSlow2G:
case WebEffectiveConnectionType::Type2G:
return true;
case WebEffectiveConnectionType::Type3G:
case WebEffectiveConnectionType::Type4G:
case WebEffectiveConnectionType::TypeUnknown:
case WebEffectiveConnectionType::TypeOffline:
return false;
}
NOTREACHED();
return false;
}
bool shouldDisallowFetchForMainFrameScript(ResourceRequest& request,
FetchRequest::DeferOption defer,
Document& document) {
// Only scripts inserted via document.write are candidates for having their
// fetch disallowed.
if (!document.isInDocumentWrite())
return false;
if (!document.settings())
return false;
if (!document.frame())
return false;
// Only block synchronously loaded (parser blocking) scripts.
if (defer != FetchRequest::NoDefer)
return false;
probe::documentWriteFetchScript(&document);
if (!request.url().protocolIsInHTTPFamily())
return false;
// Avoid blocking same origin scripts, as they may be used to render main
// page content, whereas cross-origin scripts inserted via document.write
// are likely to be third party content.
String requestHost = request.url().host();
String documentHost = document.getSecurityOrigin()->domain();
bool sameSite = false;
if (requestHost == documentHost)
sameSite = true;
// If the hosts didn't match, then see if the domains match. For example, if
// a script is served from static.example.com for a document served from
// www.example.com, we consider that a first party script and allow it.
String requestDomain = NetworkUtils::getDomainAndRegistry(
requestHost, NetworkUtils::IncludePrivateRegistries);
String documentDomain = NetworkUtils::getDomainAndRegistry(
documentHost, NetworkUtils::IncludePrivateRegistries);
// getDomainAndRegistry will return the empty string for domains that are
// already top-level, such as localhost. Thus we only compare domains if we
// get non-empty results back from getDomainAndRegistry.
if (!requestDomain.isEmpty() && !documentDomain.isEmpty() &&
requestDomain == documentDomain)
sameSite = true;
if (sameSite) {
// This histogram is introduced to help decide whether we should also check
// same scheme while deciding whether or not to block the script as is done
// in other cases of "same site" usage. On the other hand we do not want to
// block more scripts than necessary.
if (request.url().protocol() != document.getSecurityOrigin()->protocol()) {
document.loader()->didObserveLoadingBehavior(
WebLoadingBehaviorFlag::
WebLoadingBehaviorDocumentWriteBlockDifferentScheme);
}
return false;
}
emitWarningForDocWriteScripts(request.url().getString(), document);
request.setHTTPHeaderField("Intervention",
"<https://www.chromestatus.com/feature/"
"5718547946799104>; level=\"warning\"");
// Do not block scripts if it is a page reload. This is to enable pages to
// recover if blocking of a script is leading to a page break and the user
// reloads the page.
const FrameLoadType loadType = document.loader()->loadType();
if (isReloadLoadType(loadType)) {
// Recording this metric since an increase in number of reloads for pages
// where a script was blocked could be indicative of a page break.
document.loader()->didObserveLoadingBehavior(
WebLoadingBehaviorFlag::WebLoadingBehaviorDocumentWriteBlockReload);
return false;
}
// Add the metadata that this page has scripts inserted via document.write
// that are eligible for blocking. Note that if there are multiple scripts
// the flag will be conveyed to the browser process only once.
document.loader()->didObserveLoadingBehavior(
WebLoadingBehaviorFlag::WebLoadingBehaviorDocumentWriteBlock);
const bool is2G =
networkStateNotifier().connectionType() == WebConnectionTypeCellular2G;
WebEffectiveConnectionType effectiveConnection =
document.frame()->client()->getEffectiveConnectionType();
return document.settings()
->getDisallowFetchForDocWrittenScriptsInMainFrame() ||
(document.settings()
->getDisallowFetchForDocWrittenScriptsInMainFrameOnSlowConnections() &&
is2G) ||
(document.settings()
->getDisallowFetchForDocWrittenScriptsInMainFrameIfEffectively2G() &&
isConnectionEffectively2G(effectiveConnection));
}
enum class RequestMethod { kIsPost, kIsNotPost };
enum class RequestType { kIsConditional, kIsNotConditional };
enum class ResourceType { kIsMainResource, kIsNotMainResource };
WebCachePolicy determineWebCachePolicy(RequestMethod method,
RequestType requestType,
ResourceType resourceType,
FrameLoadType loadType) {
switch (loadType) {
case FrameLoadTypeStandard:
return (requestType == RequestType::kIsConditional ||
method == RequestMethod::kIsPost)
? WebCachePolicy::ValidatingCacheData
: WebCachePolicy::UseProtocolCachePolicy;
case FrameLoadTypeReplaceCurrentItem:
case FrameLoadTypeInitialInChildFrame:
// TODO(toyoshim): Should be the same with FrameLoadTypeStandard, but
// keep legacy logic as is. To be changed in a follow-up patch soon.
return (resourceType == ResourceType::kIsMainResource &&
(requestType == RequestType::kIsConditional ||
method == RequestMethod::kIsPost))
? WebCachePolicy::ValidatingCacheData
: WebCachePolicy::UseProtocolCachePolicy;
case FrameLoadTypeInitialHistoryLoad:
// TODO(toyoshim): Should be the same with FrameLoadTypeBackForward, but
// keep legacy logic as is. To be changed in a follow-up patch soon.
return (resourceType == ResourceType::kIsMainResource &&
(requestType == RequestType::kIsConditional ||
method == RequestMethod::kIsPost))
? WebCachePolicy::ValidatingCacheData
: WebCachePolicy::UseProtocolCachePolicy;
case FrameLoadTypeBackForward:
// Mutates the policy for POST requests to avoid form resubmission.
return method == RequestMethod::kIsPost
? WebCachePolicy::ReturnCacheDataDontLoad
: WebCachePolicy::ReturnCacheDataElseLoad;
case FrameLoadTypeReload:
return resourceType == ResourceType::kIsMainResource
? WebCachePolicy::ValidatingCacheData
: WebCachePolicy::UseProtocolCachePolicy;
case FrameLoadTypeReloadBypassingCache:
// TODO(toyoshim): Should return BypassingCache always, but keep legacy
// logic as is. To be changed in a follow-up patch soon.
return (resourceType == ResourceType::kIsMainResource &&
(requestType == RequestType::kIsConditional ||
method == RequestMethod::kIsPost))
? WebCachePolicy::ValidatingCacheData
: WebCachePolicy::BypassingCache;
}
NOTREACHED();
return WebCachePolicy::UseProtocolCachePolicy;
}
// TODO(toyoshim): Remove |resourceType|. See comments in
// resourceRequestCachePolicy().
WebCachePolicy determineFrameWebCachePolicy(Frame* frame,
ResourceType resourceType) {
if (!frame)
return WebCachePolicy::UseProtocolCachePolicy;
if (!frame->isLocalFrame())
return determineFrameWebCachePolicy(frame->tree().parent(), resourceType);
// Does not propagate cache policy for subresources after the load event.
// TODO(toyoshim): We should be able to remove following parents' policy check
// if each frame has a relevant FrameLoadType for reload and history
// navigations.
if (resourceType == ResourceType::kIsNotMainResource &&
toLocalFrame(frame)->document()->loadEventFinished()) {
return WebCachePolicy::UseProtocolCachePolicy;
}
// Respects BypassingCache rather than parent's policy.
// TODO(toyoshim): Adopt BypassingCache even for MainResource.
FrameLoadType loadType =
toLocalFrame(frame)->loader().documentLoader()->loadType();
if (resourceType == ResourceType::kIsNotMainResource &&
loadType == FrameLoadTypeReloadBypassingCache) {
return WebCachePolicy::BypassingCache;
}
// Respects parent's policy if it has a special one.
WebCachePolicy parentPolicy =
determineFrameWebCachePolicy(frame->tree().parent(), resourceType);
if (parentPolicy != WebCachePolicy::UseProtocolCachePolicy)
return parentPolicy;
// Otherwise, follows FrameLoadType. Use kIsNotPost, kIsNotConditional, and
// kIsNotMainResource to obtain a representative policy for the frame.
return determineWebCachePolicy(RequestMethod::kIsNotPost,
RequestType::kIsNotConditional,
ResourceType::kIsNotMainResource, loadType);
}
} // namespace
FrameFetchContext::FrameFetchContext(DocumentLoader* loader, Document* document)
: m_document(document), m_documentLoader(loader) {
DCHECK(frame());
}
FrameFetchContext::~FrameFetchContext() {
m_document = nullptr;
m_documentLoader = nullptr;
}
LocalFrame* FrameFetchContext::frameOfImportsController() const {
DCHECK(m_document);
HTMLImportsController* importsController = m_document->importsController();
DCHECK(importsController);
LocalFrame* frame = importsController->master()->frame();
DCHECK(frame);
return frame;
}
LocalFrame* FrameFetchContext::frame() const {
if (!m_documentLoader)
return frameOfImportsController();
LocalFrame* frame = m_documentLoader->frame();
DCHECK(frame);
return frame;
}
LocalFrameClient* FrameFetchContext::localFrameClient() const {
return frame()->client();
}
void FrameFetchContext::addAdditionalRequestHeaders(ResourceRequest& request,
FetchResourceType type) {
bool isMainResource = type == FetchMainResource;
if (!isMainResource) {
if (!request.didSetHTTPReferrer()) {
DCHECK(m_document);
request.setHTTPReferrer(SecurityPolicy::generateReferrer(
m_document->getReferrerPolicy(), request.url(),
m_document->outgoingReferrer()));
request.addHTTPOriginIfNeeded(m_document->getSecurityOrigin());
} else {
DCHECK_EQ(SecurityPolicy::generateReferrer(request.getReferrerPolicy(),
request.url(),
request.httpReferrer())
.referrer,
request.httpReferrer());
request.addHTTPOriginIfNeeded(request.httpReferrer());
}
}
if (m_document) {
request.setExternalRequestStateFromRequestorAddressSpace(
m_document->addressSpace());
}
// The remaining modifications are only necessary for HTTP and HTTPS.
if (!request.url().isEmpty() && !request.url().protocolIsInHTTPFamily())
return;
// Reload should reflect the current data saver setting.
if (isReloadLoadType(masterDocumentLoader()->loadType()))
request.clearHTTPHeaderField("Save-Data");
if (frame()->settings() && frame()->settings()->getDataSaverEnabled())
request.setHTTPHeaderField("Save-Data", "on");
}
WebCachePolicy FrameFetchContext::resourceRequestCachePolicy(
ResourceRequest& request,
Resource::Type type,
FetchRequest::DeferOption defer) const {
DCHECK(frame());
if (type == Resource::MainResource) {
const WebCachePolicy cachePolicy = determineWebCachePolicy(
request.httpMethod() == "POST" ? RequestMethod::kIsPost
: RequestMethod::kIsNotPost,
request.isConditional() ? RequestType::kIsConditional
: RequestType::kIsNotConditional,
ResourceType::kIsMainResource, masterDocumentLoader()->loadType());
// Follows the parent frame's policy.
// TODO(toyoshim): Probably, FrameLoadType for each frame should have a
// right type for reload or history navigations, and should not need to
// check parent's frame policy here. Once it has a right FrameLoadType,
// we can remove Resource::Type argument from determineFrameWebCachePolicy.
// See also crbug.com/332602.
if (cachePolicy != WebCachePolicy::UseProtocolCachePolicy)
return cachePolicy;
return determineFrameWebCachePolicy(frame()->tree().parent(),
ResourceType::kIsMainResource);
}
// For users on slow connections, we want to avoid blocking the parser in
// the main frame on script loads inserted via document.write, since it can
// add significant delays before page content is displayed on the screen.
// TODO(toyoshim): Move following logic that rewrites ResourceRequest to
// somewhere that should be relevant to the script resource handling.
if (type == Resource::Script && isMainFrame() && m_document &&
shouldDisallowFetchForMainFrameScript(request, defer, *m_document))
return WebCachePolicy::ReturnCacheDataDontLoad;
// TODO(toyoshim): We should check isConditional() and use ValidatingCacheData
// only when |cachePolicy| below is UseProtocolCachePolicy.
if (request.isConditional())
return WebCachePolicy::ValidatingCacheData;
return determineFrameWebCachePolicy(frame(),
ResourceType::kIsNotMainResource);
}
// The |m_documentLoader| is null in the FrameFetchContext of an imported
// document.
// FIXME(http://crbug.com/274173): This means Inspector, which uses
// DocumentLoader as a grouping entity, cannot see imported documents.
inline DocumentLoader* FrameFetchContext::masterDocumentLoader() const {
if (m_documentLoader)
return m_documentLoader.get();
return frameOfImportsController()->loader().documentLoader();
}
void FrameFetchContext::dispatchDidChangeResourcePriority(
unsigned long identifier,
ResourceLoadPriority loadPriority,
int intraPriorityValue) {
TRACE_EVENT1(
"devtools.timeline", "ResourceChangePriority", "data",
InspectorChangeResourcePriorityEvent::data(identifier, loadPriority));
probe::didChangeResourcePriority(frame(), identifier, loadPriority);
}
void FrameFetchContext::prepareRequest(ResourceRequest& request,
RedirectType redirectType) {
frame()->loader().applyUserAgent(request);
localFrameClient()->dispatchWillSendRequest(request);
// ServiceWorker hook ups.
if (masterDocumentLoader()->getServiceWorkerNetworkProvider()) {
WrappedResourceRequest webreq(request);
masterDocumentLoader()->getServiceWorkerNetworkProvider()->willSendRequest(
webreq);
}
// If it's not for redirect, hook up ApplicationCache here too.
if (redirectType == FetchContext::RedirectType::kNotForRedirect &&
m_documentLoader && !m_documentLoader->fetcher()->archive() &&
request.url().isValid()) {
m_documentLoader->applicationCacheHost()->willStartLoading(request);
}
}
void FrameFetchContext::dispatchWillSendRequest(
unsigned long identifier,
ResourceRequest& request,
const ResourceResponse& redirectResponse,
const FetchInitiatorInfo& initiatorInfo) {
if (redirectResponse.isNull()) {
// Progress doesn't care about redirects, only notify it when an
// initial request is sent.
frame()->loader().progress().willStartLoading(identifier,
request.priority());
}
probe::willSendRequest(frame(), identifier, masterDocumentLoader(), request,
redirectResponse, initiatorInfo);
if (frame()->frameScheduler())
frame()->frameScheduler()->didStartLoading(identifier);
}
void FrameFetchContext::dispatchDidReceiveResponse(
unsigned long identifier,
const ResourceResponse& response,
WebURLRequest::FrameType frameType,
WebURLRequest::RequestContext requestContext,
Resource* resource) {
dispatchDidReceiveResponseInternal(identifier, response, frameType,
requestContext, resource,
LinkLoader::LoadResourcesAndPreconnect);
}
void FrameFetchContext::dispatchDidReceiveData(unsigned long identifier,
const char* data,
int dataLength) {
frame()->loader().progress().incrementProgress(identifier, dataLength);
probe::didReceiveData(frame(), identifier, data, dataLength);
}
void FrameFetchContext::dispatchDidReceiveEncodedData(unsigned long identifier,
int encodedDataLength) {
probe::didReceiveEncodedDataLength(frame(), identifier, encodedDataLength);
}
void FrameFetchContext::dispatchDidDownloadData(unsigned long identifier,
int dataLength,
int encodedDataLength) {
frame()->loader().progress().incrementProgress(identifier, dataLength);
probe::didReceiveData(frame(), identifier, 0, dataLength);
probe::didReceiveEncodedDataLength(frame(), identifier, encodedDataLength);
}
void FrameFetchContext::dispatchDidFinishLoading(unsigned long identifier,
double finishTime,
int64_t encodedDataLength,
int64_t decodedBodyLength) {
frame()->loader().progress().completeProgress(identifier);
probe::didFinishLoading(frame(), identifier, finishTime, encodedDataLength,
decodedBodyLength);
if (frame()->frameScheduler())
frame()->frameScheduler()->didStopLoading(identifier);
}
void FrameFetchContext::dispatchDidFail(unsigned long identifier,
const ResourceError& error,
int64_t encodedDataLength,
bool isInternalRequest) {
frame()->loader().progress().completeProgress(identifier);
probe::didFailLoading(frame(), identifier, error);
// Notification to FrameConsole should come AFTER InspectorInstrumentation
// call, DevTools front-end relies on this.
if (!isInternalRequest)
frame()->console().didFailLoading(identifier, error);
if (frame()->frameScheduler())
frame()->frameScheduler()->didStopLoading(identifier);
}
void FrameFetchContext::dispatchDidLoadResourceFromMemoryCache(
unsigned long identifier,
Resource* resource,
WebURLRequest::FrameType frameType,
WebURLRequest::RequestContext requestContext) {
ResourceRequest request(resource->url());
request.setFrameType(frameType);
request.setRequestContext(requestContext);
localFrameClient()->dispatchDidLoadResourceFromMemoryCache(
request, resource->response());
dispatchWillSendRequest(identifier, request, ResourceResponse(),
resource->options().initiatorInfo);
probe::markResourceAsCached(frame(), identifier);
if (!resource->response().isNull()) {
dispatchDidReceiveResponseInternal(identifier, resource->response(),
frameType, requestContext, resource,
LinkLoader::DoNotLoadResources);
}
if (resource->encodedSize() > 0)
dispatchDidReceiveData(identifier, 0, resource->encodedSize());
dispatchDidFinishLoading(identifier, 0, 0,
resource->response().decodedBodyLength());
}
bool FrameFetchContext::shouldLoadNewResource(Resource::Type type) const {
if (!m_documentLoader)
return true;
FrameLoader& loader = m_documentLoader->frame()->loader();
if (type == Resource::MainResource)
return m_documentLoader == loader.provisionalDocumentLoader();
return m_documentLoader == loader.documentLoader();
}
static std::unique_ptr<TracedValue>
loadResourceTraceData(unsigned long identifier, const KURL& url, int priority) {
String requestId = IdentifiersFactory::requestId(identifier);
std::unique_ptr<TracedValue> value = TracedValue::create();
value->setString("requestId", requestId);
value->setString("url", url.getString());
value->setInteger("priority", priority);
return value;
}
void FrameFetchContext::recordLoadingActivity(
unsigned long identifier,
const ResourceRequest& request,
Resource::Type type,
const AtomicString& fetchInitiatorName) {
TRACE_EVENT_ASYNC_BEGIN1(
"blink.net", "Resource", identifier, "data",
loadResourceTraceData(identifier, request.url(), request.priority()));
if (!m_documentLoader || m_documentLoader->fetcher()->archive() ||
!request.url().isValid())
return;
V8DOMActivityLogger* activityLogger = nullptr;
if (fetchInitiatorName == FetchInitiatorTypeNames::xmlhttprequest) {
activityLogger = V8DOMActivityLogger::currentActivityLogger();
} else {
activityLogger =
V8DOMActivityLogger::currentActivityLoggerIfIsolatedWorld();
}
if (activityLogger) {
Vector<String> argv;
argv.push_back(Resource::resourceTypeToString(type, fetchInitiatorName));
argv.push_back(request.url());
activityLogger->logEvent("blinkRequestResource", argv.size(), argv.data());
}
}
void FrameFetchContext::didLoadResource(Resource* resource) {
if (resource->isLoadEventBlockingResourceType())
frame()->loader().checkCompleted();
if (m_document)
FirstMeaningfulPaintDetector::from(*m_document).checkNetworkStable();
}
void FrameFetchContext::addResourceTiming(const ResourceTimingInfo& info) {
Document* initiatorDocument = m_document && info.isMainResource()
? m_document->parentDocument()
: m_document.get();
if (!initiatorDocument || !initiatorDocument->domWindow())
return;
DOMWindowPerformance::performance(*initiatorDocument->domWindow())
->addResourceTiming(info);
}
bool FrameFetchContext::allowImage(bool imagesEnabled, const KURL& url) const {
return localFrameClient()->allowImage(imagesEnabled, url);
}
void FrameFetchContext::printAccessDeniedMessage(const KURL& url) const {
if (url.isNull())
return;
String message;
if (!m_document || m_document->url().isNull()) {
message = "Unsafe attempt to load URL " + url.elidedString() + '.';
} else if (url.isLocalFile() || m_document->url().isLocalFile()) {
message = "Unsafe attempt to load URL " + url.elidedString() +
" from frame with URL " + m_document->url().elidedString() +
". 'file:' URLs are treated as unique security origins.\n";
} else {
message = "Unsafe attempt to load URL " + url.elidedString() +
" from frame with URL " + m_document->url().elidedString() +
". Domains, protocols and ports must match.\n";
}
frame()->document()->addConsoleMessage(ConsoleMessage::create(
SecurityMessageSource, ErrorMessageLevel, message));
}
ResourceRequestBlockedReason FrameFetchContext::canRequest(
Resource::Type type,
const ResourceRequest& resourceRequest,
const KURL& url,
const ResourceLoaderOptions& options,
SecurityViolationReportingPolicy reportingPolicy,
FetchRequest::OriginRestriction originRestriction) const {
ResourceRequestBlockedReason blockedReason =
canRequestInternal(type, resourceRequest, url, options, reportingPolicy,
originRestriction, resourceRequest.redirectStatus());
if (blockedReason != ResourceRequestBlockedReason::None &&
reportingPolicy == SecurityViolationReportingPolicy::Report) {
probe::didBlockRequest(frame(), resourceRequest, masterDocumentLoader(),
options.initiatorInfo, blockedReason);
}
return blockedReason;
}
ResourceRequestBlockedReason FrameFetchContext::allowResponse(
Resource::Type type,
const ResourceRequest& resourceRequest,
const KURL& url,
const ResourceLoaderOptions& options) const {
ResourceRequestBlockedReason blockedReason =
canRequestInternal(type, resourceRequest, url, options,
SecurityViolationReportingPolicy::Report,
FetchRequest::UseDefaultOriginRestrictionForType,
RedirectStatus::FollowedRedirect);
if (blockedReason != ResourceRequestBlockedReason::None) {
probe::didBlockRequest(frame(), resourceRequest, masterDocumentLoader(),
options.initiatorInfo, blockedReason);
}
return blockedReason;
}
ResourceRequestBlockedReason FrameFetchContext::canRequestInternal(
Resource::Type type,
const ResourceRequest& resourceRequest,
const KURL& url,
const ResourceLoaderOptions& options,
SecurityViolationReportingPolicy reportingPolicy,
FetchRequest::OriginRestriction originRestriction,
ResourceRequest::RedirectStatus redirectStatus) const {
if (probe::shouldBlockRequest(frame(), resourceRequest))
return ResourceRequestBlockedReason::Inspector;
SecurityOrigin* securityOrigin = options.securityOrigin.get();
if (!securityOrigin && m_document)
securityOrigin = m_document->getSecurityOrigin();
if (originRestriction != FetchRequest::NoOriginRestriction &&
securityOrigin && !securityOrigin->canDisplay(url)) {
if (reportingPolicy == SecurityViolationReportingPolicy::Report)
FrameLoader::reportLocalLoadFailed(frame(), url.elidedString());
RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::requestResource URL was not "
"allowed by SecurityOrigin::canDisplay";
return ResourceRequestBlockedReason::Other;
}
// Some types of resources can be loaded only from the same origin. Other
// types of resources, like Images, Scripts, and CSS, can be loaded from
// any URL.
switch (type) {
case Resource::MainResource:
case Resource::Image:
case Resource::CSSStyleSheet:
case Resource::Script:
case Resource::Font:
case Resource::Raw:
case Resource::LinkPrefetch:
case Resource::TextTrack:
case Resource::ImportResource:
case Resource::Media:
case Resource::Manifest:
case Resource::Mock:
// By default these types of resources can be loaded from any origin.
// FIXME: Are we sure about Resource::Font?
if (originRestriction == FetchRequest::RestrictToSameOrigin &&
!securityOrigin->canRequest(url)) {
printAccessDeniedMessage(url);
return ResourceRequestBlockedReason::Origin;
}
break;
case Resource::XSLStyleSheet:
DCHECK(RuntimeEnabledFeatures::xsltEnabled());
case Resource::SVGDocument:
if (!securityOrigin->canRequest(url)) {
printAccessDeniedMessage(url);
return ResourceRequestBlockedReason::Origin;
}
break;
}
// FIXME: Convert this to check the isolated world's Content Security Policy
// once webkit.org/b/104520 is solved.
bool shouldBypassMainWorldCSP =
frame()->script().shouldBypassMainWorldCSP() ||
options.contentSecurityPolicyOption == DoNotCheckContentSecurityPolicy;
if (m_document) {
DCHECK(m_document->contentSecurityPolicy());
if (!shouldBypassMainWorldCSP &&
!m_document->contentSecurityPolicy()->allowRequest(
resourceRequest.requestContext(), url,
options.contentSecurityPolicyNonce, options.integrityMetadata,
options.parserDisposition, redirectStatus, reportingPolicy))
return ResourceRequestBlockedReason::CSP;
}
if (type == Resource::Script || type == Resource::ImportResource) {
DCHECK(frame());
if (!localFrameClient()->allowScriptFromSource(
!frame()->settings() || frame()->settings()->getScriptEnabled(),
url)) {
localFrameClient()->didNotAllowScript();
// TODO(estark): Use a different ResourceRequestBlockedReason here, since
// this check has nothing to do with CSP. https://crbug.com/600795
return ResourceRequestBlockedReason::CSP;
}
}
// SVG Images have unique security rules that prevent all subresource requests
// except for data urls.
if (type != Resource::MainResource &&
frame()->chromeClient().isSVGImageChromeClient() && !url.protocolIsData())
return ResourceRequestBlockedReason::Origin;
// Measure the number of legacy URL schemes ('ftp://') and the number of
// embedded-credential ('http://user:password@...') resources embedded as
// subresources.
if (resourceRequest.frameType() != WebURLRequest::FrameTypeTopLevel) {
DCHECK(frame()->document());
if (SchemeRegistry::shouldTreatURLSchemeAsLegacy(url.protocol()) &&
!SchemeRegistry::shouldTreatURLSchemeAsLegacy(
frame()->document()->getSecurityOrigin()->protocol())) {
Deprecation::countDeprecation(
frame()->document(), UseCounter::LegacyProtocolEmbeddedAsSubresource);
// TODO(mkwst): Enabled by default in M59. Drop the runtime-enabled check
// in M60: https://www.chromestatus.com/feature/5709390967472128
if (RuntimeEnabledFeatures::blockLegacySubresourcesEnabled())
return ResourceRequestBlockedReason::Origin;
}
if (!url.user().isEmpty() || !url.pass().isEmpty()) {
Deprecation::countDeprecation(
frame()->document(),
UseCounter::RequestedSubresourceWithEmbeddedCredentials);
// TODO(mkwst): Remove the runtime-enabled check in M59:
// https://www.chromestatus.com/feature/5669008342777856
if (RuntimeEnabledFeatures::blockCredentialedSubresourcesEnabled())
return ResourceRequestBlockedReason::Origin;
}
}
// Check for mixed content. We do this second-to-last so that when folks block
// mixed content with a CSP policy, they don't get a warning. They'll still
// get a warning in the console about CSP blocking the load.
if (MixedContentChecker::shouldBlockFetch(frame(), resourceRequest, url,
reportingPolicy))
return ResourceRequestBlockedReason::MixedContent;
// Let the client have the final say into whether or not the load should
// proceed.
DocumentLoader* documentLoader = masterDocumentLoader();
if (documentLoader && documentLoader->subresourceFilter() &&
type != Resource::MainResource && type != Resource::ImportResource) {
if (!documentLoader->subresourceFilter()->allowLoad(
url, resourceRequest.requestContext(), reportingPolicy)) {
return ResourceRequestBlockedReason::SubresourceFilter;
}
}
return ResourceRequestBlockedReason::None;
}
bool FrameFetchContext::isControlledByServiceWorker() const {
DCHECK(masterDocumentLoader());
// Service workers are bypassed by suborigins (see
// https://w3c.github.io/webappsec-suborigins/). Since service worker
// controllers are assigned based on physical origin, without knowledge of
// whether the context is in a suborigin, it is necessary to explicitly bypass
// service workers on a per-request basis. Additionally, it is necessary to
// explicitly return |false| here so that it is clear that the SW will be
// bypassed. In particular, this is important for
// ResourceFetcher::getCacheIdentifier(), which will return the SW's cache if
// the context's isControlledByServiceWorker() returns |true|, and thus will
// returned cached resources from the service worker. That would have the
// effect of not bypassing the SW.
if (getSecurityOrigin() && getSecurityOrigin()->hasSuborigin())
return false;
auto* service_worker_network_provider =
masterDocumentLoader()->getServiceWorkerNetworkProvider();
return service_worker_network_provider &&
service_worker_network_provider->isControlledByServiceWorker();
}
int64_t FrameFetchContext::serviceWorkerID() const {
DCHECK(masterDocumentLoader());
auto* service_worker_network_provider =
masterDocumentLoader()->getServiceWorkerNetworkProvider();
return service_worker_network_provider
? service_worker_network_provider->serviceWorkerID()
: -1;
}
bool FrameFetchContext::isMainFrame() const {
return frame()->isMainFrame();
}
bool FrameFetchContext::defersLoading() const {
return frame()->page()->suspended();
}
bool FrameFetchContext::isLoadComplete() const {
return m_document && m_document->loadEventFinished();
}
bool FrameFetchContext::pageDismissalEventBeingDispatched() const {
return m_document &&
m_document->pageDismissalEventBeingDispatched() !=
Document::NoDismissal;
}
bool FrameFetchContext::updateTimingInfoForIFrameNavigation(
ResourceTimingInfo* info) {
// <iframe>s should report the initial navigation requested by the parent
// document, but not subsequent navigations.
// FIXME: Resource timing is broken when the parent is a remote frame.
if (!frame()->deprecatedLocalOwner() ||
frame()->deprecatedLocalOwner()->loadedNonEmptyDocument())
return false;
frame()->deprecatedLocalOwner()->didLoadNonEmptyDocument();
// Do not report iframe navigation that restored from history, since its
// location may have been changed after initial navigation.
if (masterDocumentLoader()->loadType() == FrameLoadTypeInitialHistoryLoad)
return false;
info->setInitiatorType(frame()->deprecatedLocalOwner()->localName());
return true;
}
void FrameFetchContext::sendImagePing(const KURL& url) {
PingLoader::loadImage(frame(), url);
}
void FrameFetchContext::addConsoleMessage(const String& message,
LogMessageType messageType) const {
MessageLevel level = messageType == LogWarningMessage ? WarningMessageLevel
: ErrorMessageLevel;
if (frame()->document()) {
frame()->document()->addConsoleMessage(
ConsoleMessage::create(JSMessageSource, level, message));
}
}
SecurityOrigin* FrameFetchContext::getSecurityOrigin() const {
return m_document ? m_document->getSecurityOrigin() : nullptr;
}
void FrameFetchContext::modifyRequestForCSP(ResourceRequest& resourceRequest) {
// Record the latest requiredCSP value that will be used when sending this
// request.
frame()->loader().recordLatestRequiredCSP();
frame()->loader().modifyRequestForCSP(resourceRequest, m_document);
}
void FrameFetchContext::addClientHintsIfNecessary(
const ClientHintsPreferences& hintsPreferences,
const FetchRequest::ResourceWidth& resourceWidth,
ResourceRequest& request) {
if (!RuntimeEnabledFeatures::clientHintsEnabled() || !m_document)
return;
bool shouldSendDPR = m_document->clientHintsPreferences().shouldSendDPR() ||
hintsPreferences.shouldSendDPR();
bool shouldSendResourceWidth =
m_document->clientHintsPreferences().shouldSendResourceWidth() ||
hintsPreferences.shouldSendResourceWidth();
bool shouldSendViewportWidth =
m_document->clientHintsPreferences().shouldSendViewportWidth() ||
hintsPreferences.shouldSendViewportWidth();
if (shouldSendDPR) {
request.addHTTPHeaderField(
"DPR", AtomicString(String::number(m_document->devicePixelRatio())));
}
if (shouldSendResourceWidth) {
if (resourceWidth.isSet) {
float physicalWidth =
resourceWidth.width * m_document->devicePixelRatio();
request.addHTTPHeaderField(
"Width", AtomicString(String::number(ceil(physicalWidth))));
}
}
if (shouldSendViewportWidth && frame()->view()) {
request.addHTTPHeaderField(
"Viewport-Width",
AtomicString(String::number(frame()->view()->viewportWidth())));
}
}
void FrameFetchContext::addCSPHeaderIfNecessary(Resource::Type type,
ResourceRequest& request) {
if (!m_document)
return;
const ContentSecurityPolicy* csp = m_document->contentSecurityPolicy();
if (csp->shouldSendCSPHeader(type))
request.addHTTPHeaderField("CSP", "active");
}
void FrameFetchContext::populateResourceRequest(
Resource::Type type,
const ClientHintsPreferences& hintsPreferences,
const FetchRequest::ResourceWidth& resourceWidth,
ResourceRequest& request) {
setFirstPartyCookieAndRequestorOrigin(request);
modifyRequestForCSP(request);
addClientHintsIfNecessary(hintsPreferences, resourceWidth, request);
addCSPHeaderIfNecessary(type, request);
}
void FrameFetchContext::setFirstPartyCookieAndRequestorOrigin(
ResourceRequest& request) {
if (!m_document)
return;
if (request.firstPartyForCookies().isNull()) {
request.setFirstPartyForCookies(
m_document ? m_document->firstPartyForCookies()
: SecurityOrigin::urlWithUniqueSecurityOrigin());
}
// Subresource requests inherit their requestor origin from |m_document|
// directly. Top-level and nested frame types are taken care of in
// 'FrameLoadRequest()'. Auxiliary frame types in 'createWindow()' and
// 'FrameLoader::load'.
// TODO(mkwst): It would be cleaner to adjust blink::ResourceRequest to
// initialize itself with a `nullptr` initiator so that this can be a simple
// `isNull()` check. https://crbug.com/625969
if (request.frameType() == WebURLRequest::FrameTypeNone &&
request.requestorOrigin()->isUnique()) {
request.setRequestorOrigin(m_document->isSandboxed(SandboxOrigin)
? SecurityOrigin::create(m_document->url())
: m_document->getSecurityOrigin());
}
}
MHTMLArchive* FrameFetchContext::archive() const {
DCHECK(!isMainFrame());
// TODO(nasko): How should this work with OOPIF?
// The MHTMLArchive is parsed as a whole, but can be constructed from frames
// in mutliple processes. In that case, which process should parse it and how
// should the output be spread back across multiple processes?
if (!frame()->tree().parent()->isLocalFrame())
return nullptr;
return toLocalFrame(frame()->tree().parent())
->loader()
.documentLoader()
->fetcher()
->archive();
}
ResourceLoadPriority FrameFetchContext::modifyPriorityForExperiments(
ResourceLoadPriority priority) {
// If Settings is null, we can't verify any experiments are in force.
if (!frame()->settings())
return priority;
// If enabled, drop the priority of all resources in a subframe.
if (frame()->settings()->getLowPriorityIframes() && !frame()->isMainFrame())
return ResourceLoadPriorityVeryLow;
return priority;
}
RefPtr<WebTaskRunner> FrameFetchContext::loadingTaskRunner() const {
return frame()->frameScheduler()->loadingTaskRunner();
}
void FrameFetchContext::dispatchDidReceiveResponseInternal(
unsigned long identifier,
const ResourceResponse& response,
WebURLRequest::FrameType frameType,
WebURLRequest::RequestContext requestContext,
Resource* resource,
LinkLoader::CanLoadResources resourceLoadingPolicy) {
MixedContentChecker::checkMixedPrivatePublic(frame(),
response.remoteIPAddress());
if (m_documentLoader &&
m_documentLoader ==
m_documentLoader->frame()->loader().provisionalDocumentLoader()) {
FrameClientHintsPreferencesContext hintsContext(frame());
m_documentLoader->clientHintsPreferences()
.updateFromAcceptClientHintsHeader(
response.httpHeaderField(HTTPNames::Accept_CH), &hintsContext);
// When response is received with a provisional docloader, the resource
// haven't committed yet, and we cannot load resources, only preconnect.
resourceLoadingPolicy = LinkLoader::DoNotLoadResources;
}
LinkLoader::loadLinksFromHeader(
response.httpHeaderField(HTTPNames::Link), response.url(),
frame()->document(), NetworkHintsInterfaceImpl(), resourceLoadingPolicy,
LinkLoader::LoadAll, nullptr);
if (response.hasMajorCertificateErrors()) {
MixedContentChecker::handleCertificateError(frame(), response, frameType,
requestContext);
}
frame()->loader().progress().incrementProgress(identifier, response);
localFrameClient()->dispatchDidReceiveResponse(response);
DocumentLoader* documentLoader = masterDocumentLoader();
probe::didReceiveResourceResponse(frame(), identifier, documentLoader,
response, resource);
// It is essential that inspector gets resource response BEFORE console.
frame()->console().reportResourceResponseReceived(documentLoader, identifier,
response);
}
DEFINE_TRACE(FrameFetchContext) {
visitor->trace(m_document);
visitor->trace(m_documentLoader);
FetchContext::trace(visitor);
}
} // namespace blink