blob: 295d433d0e978c2b73b6b0ac42003da62da698ea [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/ContentSettingsClient.h"
#include "core/frame/Deprecation.h"
#include "core/frame/FrameConsole.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/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/probe/CoreProbes.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/wtf/Vector.h"
#include "public/platform/WebCachePolicy.h"
#include "public/platform/WebInsecureRequestPolicy.h"
#include "public/platform/WebViewScheduler.h"
#include "public/platform/modules/serviceworker/WebServiceWorkerNetworkProvider.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(kJSMessageSource, kWarningMessageLevel, message));
WTFLogAlways("%s", message.Utf8().data());
}
bool IsConnectionEffectively2G(WebEffectiveConnectionType effective_type) {
switch (effective_type) {
case WebEffectiveConnectionType::kTypeSlow2G:
case WebEffectiveConnectionType::kType2G:
return true;
case WebEffectiveConnectionType::kType3G:
case WebEffectiveConnectionType::kType4G:
case WebEffectiveConnectionType::kTypeUnknown:
case WebEffectiveConnectionType::kTypeOffline:
return false;
}
NOTREACHED();
return false;
}
bool ShouldDisallowFetchForMainFrameScript(ResourceRequest& request,
FetchParameters::DeferOption defer,
Document& document) {
// Only scripts inserted via document.write are candidates for having their
// fetch disallowed.
if (!document.IsInDocumentWrite())
return false;
if (!document.GetSettings())
return false;
if (!document.GetFrame())
return false;
// Only block synchronously loaded (parser blocking) scripts.
if (defer != FetchParameters::kNoDefer)
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 request_host = request.Url().Host();
String document_host = document.GetSecurityOrigin()->Domain();
bool same_site = false;
if (request_host == document_host)
same_site = 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 request_domain = NetworkUtils::GetDomainAndRegistry(
request_host, NetworkUtils::kIncludePrivateRegistries);
String document_domain = NetworkUtils::GetDomainAndRegistry(
document_host, NetworkUtils::kIncludePrivateRegistries);
// 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 (!request_domain.IsEmpty() && !document_domain.IsEmpty() &&
request_domain == document_domain)
same_site = true;
if (same_site) {
// 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::
kWebLoadingBehaviorDocumentWriteBlockDifferentScheme);
}
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 load_type = document.Loader()->LoadType();
if (IsReloadLoadType(load_type)) {
// 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::kWebLoadingBehaviorDocumentWriteBlockReload);
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::kWebLoadingBehaviorDocumentWriteBlock);
const bool is2g = GetNetworkStateNotifier().ConnectionType() ==
kWebConnectionTypeCellular2G;
WebEffectiveConnectionType effective_connection =
document.GetFrame()->Client()->GetEffectiveConnectionType();
return document.GetSettings()
->GetDisallowFetchForDocWrittenScriptsInMainFrame() ||
(document.GetSettings()
->GetDisallowFetchForDocWrittenScriptsInMainFrameOnSlowConnections() &&
is2g) ||
(document.GetSettings()
->GetDisallowFetchForDocWrittenScriptsInMainFrameIfEffectively2G() &&
IsConnectionEffectively2G(effective_connection));
}
enum class RequestMethod { kIsPost, kIsNotPost };
enum class RequestType { kIsConditional, kIsNotConditional };
enum class ResourceType { kIsMainResource, kIsNotMainResource };
// Determines WebCachePolicy for a main resource, or WebCachePolicy that is
// corresponding to FrameLoadType.
// TODO(toyoshim): Probably, we should split FrameLoadType to WebCachePolicy
// conversion logic into a separate function.
WebCachePolicy DetermineWebCachePolicy(RequestMethod method,
RequestType request_type,
ResourceType resource_type,
FrameLoadType load_type) {
switch (load_type) {
case kFrameLoadTypeStandard:
case kFrameLoadTypeReplaceCurrentItem:
case kFrameLoadTypeInitialInChildFrame:
return (request_type == RequestType::kIsConditional ||
method == RequestMethod::kIsPost)
? WebCachePolicy::kValidatingCacheData
: WebCachePolicy::kUseProtocolCachePolicy;
case kFrameLoadTypeBackForward:
case kFrameLoadTypeInitialHistoryLoad:
// Mutates the policy for POST requests to avoid form resubmission.
return method == RequestMethod::kIsPost
? WebCachePolicy::kReturnCacheDataDontLoad
: WebCachePolicy::kReturnCacheDataElseLoad;
case kFrameLoadTypeReload:
return resource_type == ResourceType::kIsMainResource
? WebCachePolicy::kValidatingCacheData
: WebCachePolicy::kUseProtocolCachePolicy;
case kFrameLoadTypeReloadBypassingCache:
return WebCachePolicy::kBypassingCache;
}
NOTREACHED();
return WebCachePolicy::kUseProtocolCachePolicy;
}
// Determines WebCachePolicy for |frame|. This WebCachePolicy should be a base
// policy to consider one of each resource belonging to the frame, and should
// not count resource specific conditions in.
// TODO(toyoshim): Remove |resourceType| to realize the design described above.
// See also comments in resourceRequestCachePolicy().
WebCachePolicy DetermineFrameWebCachePolicy(Frame* frame,
ResourceType resource_type) {
if (!frame)
return WebCachePolicy::kUseProtocolCachePolicy;
if (!frame->IsLocalFrame())
return DetermineFrameWebCachePolicy(frame->Tree().Parent(), resource_type);
// 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 (resource_type == ResourceType::kIsNotMainResource &&
ToLocalFrame(frame)->GetDocument()->LoadEventFinished()) {
return WebCachePolicy::kUseProtocolCachePolicy;
}
// Respects BypassingCache rather than parent's policy.
FrameLoadType load_type =
ToLocalFrame(frame)->Loader().GetDocumentLoader()->LoadType();
if (load_type == kFrameLoadTypeReloadBypassingCache)
return WebCachePolicy::kBypassingCache;
// Respects parent's policy if it has a special one.
WebCachePolicy parent_policy =
DetermineFrameWebCachePolicy(frame->Tree().Parent(), resource_type);
if (parent_policy != WebCachePolicy::kUseProtocolCachePolicy)
return parent_policy;
// Otherwise, follows FrameLoadType. Use kIsNotPost, kIsNotConditional, and
// kIsNotMainResource to obtain a representative policy for the frame.
return DetermineWebCachePolicy(RequestMethod::kIsNotPost,
RequestType::kIsNotConditional,
ResourceType::kIsNotMainResource, load_type);
}
} // namespace
FrameFetchContext::FrameFetchContext(DocumentLoader* loader, Document* document)
: BaseFetchContext(document), document_loader_(loader) {
DCHECK(GetFrame());
}
void FrameFetchContext::ProvideDocumentToContext(FetchContext& context,
Document* document) {
DCHECK(document);
CHECK(context.IsFrameFetchContext());
static_cast<FrameFetchContext&>(context).execution_context_ = document;
}
FrameFetchContext::~FrameFetchContext() {
document_loader_ = nullptr;
}
LocalFrame* FrameFetchContext::FrameOfImportsController() const {
DCHECK(GetDocument());
HTMLImportsController* imports_controller =
GetDocument()->ImportsController();
DCHECK(imports_controller);
LocalFrame* frame = imports_controller->Master()->GetFrame();
DCHECK(frame);
return frame;
}
Document* FrameFetchContext::GetDocument() const {
return ToDocument(execution_context_.Get());
}
LocalFrame* FrameFetchContext::GetFrame() const {
if (!document_loader_)
return FrameOfImportsController();
LocalFrame* frame = document_loader_->GetFrame();
DCHECK(frame);
return frame;
}
LocalFrameClient* FrameFetchContext::GetLocalFrameClient() const {
return GetFrame()->Client();
}
ContentSettingsClient* FrameFetchContext::GetContentSettingsClient() const {
return GetFrame()->GetContentSettingsClient();
}
void FrameFetchContext::AddAdditionalRequestHeaders(ResourceRequest& request,
FetchResourceType type) {
BaseFetchContext::AddAdditionalRequestHeaders(request, type);
// 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 (GetFrame()->GetSettings() &&
GetFrame()->GetSettings()->GetDataSaverEnabled())
request.SetHTTPHeaderField("Save-Data", "on");
}
WebCachePolicy FrameFetchContext::ResourceRequestCachePolicy(
ResourceRequest& request,
Resource::Type type,
FetchParameters::DeferOption defer) const {
DCHECK(GetFrame());
if (type == Resource::kMainResource) {
const WebCachePolicy cache_policy = 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 (cache_policy != WebCachePolicy::kUseProtocolCachePolicy)
return cache_policy;
return DetermineFrameWebCachePolicy(GetFrame()->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::kScript && IsMainFrame() && GetDocument() &&
ShouldDisallowFetchForMainFrameScript(request, defer, *GetDocument()))
return WebCachePolicy::kReturnCacheDataDontLoad;
const WebCachePolicy cache_policy = DetermineFrameWebCachePolicy(
GetFrame(), ResourceType::kIsNotMainResource);
// TODO(toyoshim): Revisit to consider if this clause can be merged to
// determineWebCachePolicy or determineFrameWebCachePolicy.
if (cache_policy == WebCachePolicy::kUseProtocolCachePolicy &&
request.IsConditional()) {
return WebCachePolicy::kValidatingCacheData;
}
return cache_policy;
}
// 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 (document_loader_)
return document_loader_.Get();
return FrameOfImportsController()->Loader().GetDocumentLoader();
}
void FrameFetchContext::DispatchDidChangeResourcePriority(
unsigned long identifier,
ResourceLoadPriority load_priority,
int intra_priority_value) {
TRACE_EVENT1(
"devtools.timeline", "ResourceChangePriority", "data",
InspectorChangeResourcePriorityEvent::Data(identifier, load_priority));
probe::didChangeResourcePriority(GetFrame(), identifier, load_priority);
}
void FrameFetchContext::PrepareRequest(ResourceRequest& request,
RedirectType redirect_type) {
GetFrame()->Loader().ApplyUserAgent(request);
GetLocalFrameClient()->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 (redirect_type == FetchContext::RedirectType::kNotForRedirect &&
document_loader_ && !document_loader_->Fetcher()->Archive() &&
request.Url().IsValid()) {
document_loader_->GetApplicationCacheHost()->WillStartLoading(request);
}
}
void FrameFetchContext::DispatchWillSendRequest(
unsigned long identifier,
ResourceRequest& request,
const ResourceResponse& redirect_response,
const FetchInitiatorInfo& initiator_info) {
if (redirect_response.IsNull()) {
// Progress doesn't care about redirects, only notify it when an
// initial request is sent.
GetFrame()->Loader().Progress().WillStartLoading(identifier,
request.Priority());
}
probe::willSendRequest(GetFrame(), identifier, MasterDocumentLoader(),
request, redirect_response, initiator_info);
if (GetFrame()->FrameScheduler())
GetFrame()->FrameScheduler()->DidStartLoading(identifier);
}
void FrameFetchContext::DispatchDidReceiveResponse(
unsigned long identifier,
const ResourceResponse& response,
WebURLRequest::FrameType frame_type,
WebURLRequest::RequestContext request_context,
Resource* resource,
ResourceResponseType response_type) {
if (response_type == ResourceResponseType::kFromMemoryCache) {
// Note: probe::willSendRequest needs to precede before this probe method.
probe::markResourceAsCached(GetFrame(), identifier);
if (response.IsNull())
return;
}
MixedContentChecker::CheckMixedPrivatePublic(GetFrame(),
response.RemoteIPAddress());
LinkLoader::CanLoadResources resource_loading_policy =
response_type == ResourceResponseType::kFromMemoryCache
? LinkLoader::kDoNotLoadResources
: LinkLoader::kLoadResourcesAndPreconnect;
if (document_loader_ &&
document_loader_ ==
document_loader_->GetFrame()->Loader().ProvisionalDocumentLoader()) {
FrameClientHintsPreferencesContext hints_context(GetFrame());
document_loader_->GetClientHintsPreferences()
.UpdateFromAcceptClientHintsHeader(
response.HttpHeaderField(HTTPNames::Accept_CH), &hints_context);
// When response is received with a provisional docloader, the resource
// haven't committed yet, and we cannot load resources, only preconnect.
resource_loading_policy = LinkLoader::kDoNotLoadResources;
}
LinkLoader::LoadLinksFromHeader(
response.HttpHeaderField(HTTPNames::Link), response.Url(),
GetFrame()->GetDocument(), NetworkHintsInterfaceImpl(),
resource_loading_policy, LinkLoader::kLoadAll, nullptr);
if (response.HasMajorCertificateErrors()) {
MixedContentChecker::HandleCertificateError(GetFrame(), response,
frame_type, request_context);
}
GetFrame()->Loader().Progress().IncrementProgress(identifier, response);
GetLocalFrameClient()->DispatchDidReceiveResponse(response);
DocumentLoader* document_loader = MasterDocumentLoader();
probe::didReceiveResourceResponse(GetFrame(), identifier, document_loader,
response, resource);
// It is essential that inspector gets resource response BEFORE console.
GetFrame()->Console().ReportResourceResponseReceived(document_loader,
identifier, response);
}
void FrameFetchContext::DispatchDidReceiveData(unsigned long identifier,
const char* data,
int data_length) {
GetFrame()->Loader().Progress().IncrementProgress(identifier, data_length);
probe::didReceiveData(GetFrame(), identifier, data, data_length);
}
void FrameFetchContext::DispatchDidReceiveEncodedData(unsigned long identifier,
int encoded_data_length) {
probe::didReceiveEncodedDataLength(GetFrame(), identifier,
encoded_data_length);
}
void FrameFetchContext::DispatchDidDownloadData(unsigned long identifier,
int data_length,
int encoded_data_length) {
GetFrame()->Loader().Progress().IncrementProgress(identifier, data_length);
probe::didReceiveData(GetFrame(), identifier, 0, data_length);
probe::didReceiveEncodedDataLength(GetFrame(), identifier,
encoded_data_length);
}
void FrameFetchContext::DispatchDidFinishLoading(unsigned long identifier,
double finish_time,
int64_t encoded_data_length,
int64_t decoded_body_length) {
GetFrame()->Loader().Progress().CompleteProgress(identifier);
probe::didFinishLoading(GetFrame(), identifier, finish_time,
encoded_data_length, decoded_body_length);
if (GetFrame()->FrameScheduler())
GetFrame()->FrameScheduler()->DidStopLoading(identifier);
}
void FrameFetchContext::DispatchDidFail(unsigned long identifier,
const ResourceError& error,
int64_t encoded_data_length,
bool is_internal_request) {
GetFrame()->Loader().Progress().CompleteProgress(identifier);
probe::didFailLoading(GetFrame(), identifier, error);
// Notification to FrameConsole should come AFTER InspectorInstrumentation
// call, DevTools front-end relies on this.
if (!is_internal_request)
GetFrame()->Console().DidFailLoading(identifier, error);
if (GetFrame()->FrameScheduler())
GetFrame()->FrameScheduler()->DidStopLoading(identifier);
}
void FrameFetchContext::DispatchDidLoadResourceFromMemoryCache(
unsigned long identifier,
const ResourceRequest& resource_request,
const ResourceResponse& resource_response) {
GetLocalFrameClient()->DispatchDidLoadResourceFromMemoryCache(
resource_request, resource_response);
}
bool FrameFetchContext::ShouldLoadNewResource(Resource::Type type) const {
if (!document_loader_)
return true;
FrameLoader& loader = document_loader_->GetFrame()->Loader();
if (type == Resource::kMainResource)
return document_loader_ == loader.ProvisionalDocumentLoader();
return document_loader_ == loader.GetDocumentLoader();
}
static std::unique_ptr<TracedValue>
LoadResourceTraceData(unsigned long identifier, const KURL& url, int priority) {
String request_id = IdentifiersFactory::RequestId(identifier);
std::unique_ptr<TracedValue> value = TracedValue::Create();
value->SetString("requestId", request_id);
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& fetch_initiator_name) {
TRACE_EVENT_ASYNC_BEGIN1(
"blink.net", "Resource", identifier, "data",
LoadResourceTraceData(identifier, request.Url(), request.Priority()));
if (!document_loader_ || document_loader_->Fetcher()->Archive() ||
!request.Url().IsValid())
return;
V8DOMActivityLogger* activity_logger = nullptr;
if (fetch_initiator_name == FetchInitiatorTypeNames::xmlhttprequest) {
activity_logger = V8DOMActivityLogger::CurrentActivityLogger();
} else {
activity_logger =
V8DOMActivityLogger::CurrentActivityLoggerIfIsolatedWorld();
}
if (activity_logger) {
Vector<String> argv;
argv.push_back(Resource::ResourceTypeToString(type, fetch_initiator_name));
argv.push_back(request.Url());
activity_logger->LogEvent("blinkRequestResource", argv.size(), argv.data());
}
}
void FrameFetchContext::DidLoadResource(Resource* resource) {
if (!GetDocument())
return;
FirstMeaningfulPaintDetector::From(*GetDocument()).CheckNetworkStable();
if (resource->IsLoadEventBlockingResourceType())
GetDocument()->CheckCompleted();
}
void FrameFetchContext::AddResourceTiming(const ResourceTimingInfo& info) {
Document* initiator_document = GetDocument() && info.IsMainResource()
? GetDocument()->ParentDocument()
: GetDocument();
if (!initiator_document || !initiator_document->domWindow())
return;
DOMWindowPerformance::performance(*initiator_document->domWindow())
->AddResourceTiming(info);
}
bool FrameFetchContext::AllowImage(bool images_enabled, const KURL& url) const {
return GetContentSettingsClient()->AllowImage(images_enabled, url);
}
ResourceRequestBlockedReason FrameFetchContext::CanRequest(
Resource::Type type,
const ResourceRequest& resource_request,
const KURL& url,
const ResourceLoaderOptions& options,
SecurityViolationReportingPolicy reporting_policy,
FetchParameters::OriginRestriction origin_restriction) const {
ResourceRequestBlockedReason blocked_reason = CanRequestInternal(
type, resource_request, url, options, reporting_policy,
origin_restriction, resource_request.GetRedirectStatus());
if (blocked_reason != ResourceRequestBlockedReason::kNone &&
reporting_policy == SecurityViolationReportingPolicy::kReport) {
probe::didBlockRequest(GetFrame(), resource_request, MasterDocumentLoader(),
options.initiator_info, blocked_reason);
}
return blocked_reason;
}
ResourceRequestBlockedReason FrameFetchContext::CanFollowRedirect(
Resource::Type type,
const ResourceRequest& resource_request,
const KURL& url,
const ResourceLoaderOptions& options,
SecurityViolationReportingPolicy reporting_policy,
FetchParameters::OriginRestriction origin_restriction) const {
// CanRequestInternal checks enforced CSP, so check report-only here to ensure
// that violations are sent.
CheckCSPForRequest(resource_request, url, options, reporting_policy,
RedirectStatus::kFollowedRedirect,
ContentSecurityPolicy::CheckHeaderType::kCheckReportOnly);
return CanRequest(type, resource_request, url, options, reporting_policy,
origin_restriction);
}
ResourceRequestBlockedReason FrameFetchContext::AllowResponse(
Resource::Type type,
const ResourceRequest& resource_request,
const KURL& url,
const ResourceLoaderOptions& options) const {
// canRequestInternal only checks enforced policies: check report-only here
// to ensure violations are sent.
CheckCSPForRequest(resource_request, url, options,
SecurityViolationReportingPolicy::kReport,
RedirectStatus::kFollowedRedirect,
ContentSecurityPolicy::CheckHeaderType::kCheckReportOnly);
ResourceRequestBlockedReason blocked_reason =
CanRequestInternal(type, resource_request, url, options,
SecurityViolationReportingPolicy::kReport,
FetchParameters::kUseDefaultOriginRestrictionForType,
RedirectStatus::kFollowedRedirect);
if (blocked_reason != ResourceRequestBlockedReason::kNone) {
probe::didBlockRequest(GetFrame(), resource_request, MasterDocumentLoader(),
options.initiator_info, blocked_reason);
}
return blocked_reason;
}
ResourceRequestBlockedReason FrameFetchContext::CanRequestInternal(
Resource::Type type,
const ResourceRequest& resource_request,
const KURL& url,
const ResourceLoaderOptions& options,
SecurityViolationReportingPolicy reporting_policy,
FetchParameters::OriginRestriction origin_restriction,
ResourceRequest::RedirectStatus redirect_status) const {
bool should_block_request = false;
probe::shouldBlockRequest(GetFrame(), resource_request,
&should_block_request);
if (should_block_request)
return ResourceRequestBlockedReason::kInspector;
SecurityOrigin* security_origin = options.security_origin.Get();
if (!security_origin && execution_context_)
security_origin = execution_context_->GetSecurityOrigin();
if (origin_restriction != FetchParameters::kNoOriginRestriction &&
security_origin && !security_origin->CanDisplay(url)) {
if (reporting_policy == SecurityViolationReportingPolicy::kReport)
FrameLoader::ReportLocalLoadFailed(GetFrame(), url.ElidedString());
RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::requestResource URL was not "
"allowed by SecurityOrigin::canDisplay";
return ResourceRequestBlockedReason::kOther;
}
// 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::kMainResource:
case Resource::kImage:
case Resource::kCSSStyleSheet:
case Resource::kScript:
case Resource::kFont:
case Resource::kRaw:
case Resource::kLinkPrefetch:
case Resource::kTextTrack:
case Resource::kImportResource:
case Resource::kMedia:
case Resource::kManifest:
case Resource::kMock:
// By default these types of resources can be loaded from any origin.
// FIXME: Are we sure about Resource::Font?
if (origin_restriction == FetchParameters::kRestrictToSameOrigin &&
!security_origin->CanRequest(url)) {
PrintAccessDeniedMessage(url);
return ResourceRequestBlockedReason::kOrigin;
}
break;
case Resource::kXSLStyleSheet:
DCHECK(RuntimeEnabledFeatures::xsltEnabled());
case Resource::kSVGDocument:
if (!security_origin->CanRequest(url)) {
PrintAccessDeniedMessage(url);
return ResourceRequestBlockedReason::kOrigin;
}
break;
}
// We check the 'report-only' headers before upgrading the request (in
// populateResourceRequest). We check the enforced headers here to ensure we
// block things we ought to block.
if (CheckCSPForRequest(
resource_request, url, options, reporting_policy, redirect_status,
ContentSecurityPolicy::CheckHeaderType::kCheckEnforce) ==
ResourceRequestBlockedReason::CSP) {
return ResourceRequestBlockedReason::CSP;
}
if (type == Resource::kScript || type == Resource::kImportResource) {
DCHECK(GetFrame());
if (!GetContentSettingsClient()->AllowScriptFromSource(
!GetFrame()->GetSettings() ||
GetFrame()->GetSettings()->GetScriptEnabled(),
url)) {
GetContentSettingsClient()->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::kMainResource &&
GetFrame()->GetChromeClient().IsSVGImageChromeClient() &&
!url.ProtocolIsData())
return ResourceRequestBlockedReason::kOrigin;
// Measure the number of legacy URL schemes ('ftp://') and the number of
// embedded-credential ('http://user:password@...') resources embedded as
// subresources.
if (resource_request.GetFrameType() != WebURLRequest::kFrameTypeTopLevel) {
DCHECK(GetFrame()->GetDocument());
if (SchemeRegistry::ShouldTreatURLSchemeAsLegacy(url.Protocol()) &&
!SchemeRegistry::ShouldTreatURLSchemeAsLegacy(
GetFrame()->GetDocument()->GetSecurityOrigin()->Protocol())) {
Deprecation::CountDeprecation(
GetFrame()->GetDocument(),
UseCounter::kLegacyProtocolEmbeddedAsSubresource);
// 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::kOrigin;
}
if ((!url.User().IsEmpty() || !url.Pass().IsEmpty()) &&
resource_request.GetRequestContext() !=
WebURLRequest::kRequestContextXMLHttpRequest) {
Deprecation::CountDeprecation(
GetFrame()->GetDocument(),
UseCounter::kRequestedSubresourceWithEmbeddedCredentials);
// TODO(mkwst): Remove the runtime-enabled check in M59:
// https://www.chromestatus.com/feature/5669008342777856
if (RuntimeEnabledFeatures::blockCredentialedSubresourcesEnabled())
return ResourceRequestBlockedReason::kOrigin;
}
}
// 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(GetFrame(), resource_request, url,
reporting_policy))
return ResourceRequestBlockedReason::kMixedContent;
if (url.WhitespaceRemoved()) {
Deprecation::CountDeprecation(
GetFrame()->GetDocument(),
UseCounter::kCanRequestURLHTTPContainingNewline);
if (url.ProtocolIsInHTTPFamily()) {
if (RuntimeEnabledFeatures::restrictCanRequestURLCharacterSetEnabled())
return ResourceRequestBlockedReason::kOther;
} else {
UseCounter::Count(GetFrame()->GetDocument(),
UseCounter::kCanRequestURLNonHTTPContainingNewline);
}
}
// Let the client have the final say into whether or not the load should
// proceed.
DocumentLoader* document_loader = MasterDocumentLoader();
if (document_loader && document_loader->GetSubresourceFilter() &&
type != Resource::kMainResource && type != Resource::kImportResource) {
if (!document_loader->GetSubresourceFilter()->AllowLoad(
url, resource_request.GetRequestContext(), reporting_policy)) {
return ResourceRequestBlockedReason::kSubresourceFilter;
}
}
return ResourceRequestBlockedReason::kNone;
}
ResourceRequestBlockedReason FrameFetchContext::CheckCSPForRequest(
const ResourceRequest& resource_request,
const KURL& url,
const ResourceLoaderOptions& options,
SecurityViolationReportingPolicy reporting_policy,
ResourceRequest::RedirectStatus redirect_status,
ContentSecurityPolicy::CheckHeaderType check_header_type) const {
if (GetFrame()->GetScriptController().ShouldBypassMainWorldCSP() ||
options.content_security_policy_option ==
kDoNotCheckContentSecurityPolicy) {
return ResourceRequestBlockedReason::kNone;
}
if (execution_context_) {
DCHECK(execution_context_->GetContentSecurityPolicy());
if (!execution_context_->GetContentSecurityPolicy()->AllowRequest(
resource_request.GetRequestContext(), url,
options.content_security_policy_nonce, options.integrity_metadata,
options.parser_disposition, redirect_status, reporting_policy,
check_header_type))
return ResourceRequestBlockedReason::CSP;
}
return ResourceRequestBlockedReason::kNone;
}
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 GetFrame()->IsMainFrame();
}
bool FrameFetchContext::DefersLoading() const {
return GetFrame()->GetPage()->Suspended();
}
bool FrameFetchContext::IsLoadComplete() const {
return GetDocument() && GetDocument()->LoadEventFinished();
}
bool FrameFetchContext::PageDismissalEventBeingDispatched() const {
return GetDocument() && GetDocument()->PageDismissalEventBeingDispatched() !=
Document::kNoDismissal;
}
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 (!GetFrame()->DeprecatedLocalOwner() ||
GetFrame()->DeprecatedLocalOwner()->LoadedNonEmptyDocument())
return false;
GetFrame()->DeprecatedLocalOwner()->DidLoadNonEmptyDocument();
// Do not report iframe navigation that restored from history, since its
// location may have been changed after initial navigation.
if (MasterDocumentLoader()->LoadType() == kFrameLoadTypeInitialHistoryLoad)
return false;
info->SetInitiatorType(GetFrame()->DeprecatedLocalOwner()->localName());
return true;
}
void FrameFetchContext::SendImagePing(const KURL& url) {
PingLoader::LoadImage(GetFrame(), url);
}
void FrameFetchContext::AddConsoleMessage(const String& message,
LogMessageType message_type) const {
MessageLevel level = message_type == kLogWarningMessage ? kWarningMessageLevel
: kErrorMessageLevel;
if (GetFrame()->GetDocument()) {
GetFrame()->GetDocument()->AddConsoleMessage(
ConsoleMessage::Create(kJSMessageSource, level, message));
}
}
void FrameFetchContext::ModifyRequestForCSP(ResourceRequest& resource_request) {
// Record the latest requiredCSP value that will be used when sending this
// request.
GetFrame()->Loader().RecordLatestRequiredCSP();
GetFrame()->Loader().ModifyRequestForCSP(resource_request, GetDocument());
}
void FrameFetchContext::AddClientHintsIfNecessary(
const ClientHintsPreferences& hints_preferences,
const FetchParameters::ResourceWidth& resource_width,
ResourceRequest& request) {
if (!RuntimeEnabledFeatures::clientHintsEnabled() || !GetDocument())
return;
bool should_send_dpr =
GetDocument()->GetClientHintsPreferences().ShouldSendDPR() ||
hints_preferences.ShouldSendDPR();
bool should_send_resource_width =
GetDocument()->GetClientHintsPreferences().ShouldSendResourceWidth() ||
hints_preferences.ShouldSendResourceWidth();
bool should_send_viewport_width =
GetDocument()->GetClientHintsPreferences().ShouldSendViewportWidth() ||
hints_preferences.ShouldSendViewportWidth();
if (should_send_dpr) {
request.AddHTTPHeaderField(
"DPR", AtomicString(String::Number(GetDocument()->DevicePixelRatio())));
}
if (should_send_resource_width) {
if (resource_width.is_set) {
float physical_width =
resource_width.width * GetDocument()->DevicePixelRatio();
request.AddHTTPHeaderField(
"Width", AtomicString(String::Number(ceil(physical_width))));
}
}
if (should_send_viewport_width && GetFrame()->View()) {
request.AddHTTPHeaderField(
"Viewport-Width",
AtomicString(String::Number(GetFrame()->View()->ViewportWidth())));
}
}
void FrameFetchContext::PopulateResourceRequest(
const KURL& url,
Resource::Type type,
const ClientHintsPreferences& hints_preferences,
const FetchParameters::ResourceWidth& resource_width,
const ResourceLoaderOptions& options,
SecurityViolationReportingPolicy reporting_policy,
ResourceRequest& request) {
SetFirstPartyCookieAndRequestorOrigin(request);
// Before modifying the request for CSP, evaluate report-only headers. This
// allows site owners to learn about requests that are being modified
// (e.g. mixed content that is being upgraded by upgrade-insecure-requests).
CheckCSPForRequest(request, url, options, reporting_policy,
request.GetRedirectStatus(),
ContentSecurityPolicy::CheckHeaderType::kCheckReportOnly);
ModifyRequestForCSP(request);
AddClientHintsIfNecessary(hints_preferences, resource_width, request);
AddCSPHeaderIfNecessary(type, request);
}
void FrameFetchContext::SetFirstPartyCookieAndRequestorOrigin(
ResourceRequest& request) {
if (!GetDocument())
return;
if (request.FirstPartyForCookies().IsNull()) {
request.SetFirstPartyForCookies(
GetDocument() ? GetDocument()->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.GetFrameType() == WebURLRequest::kFrameTypeNone &&
request.RequestorOrigin()->IsUnique()) {
request.SetRequestorOrigin(
GetDocument()->IsSandboxed(kSandboxOrigin)
? SecurityOrigin::Create(execution_context_->Url())
: execution_context_->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 (!GetFrame()->Tree().Parent()->IsLocalFrame())
return nullptr;
return ToLocalFrame(GetFrame()->Tree().Parent())
->Loader()
.GetDocumentLoader()
->Fetcher()
->Archive();
}
ResourceLoadPriority FrameFetchContext::ModifyPriorityForExperiments(
ResourceLoadPriority priority) {
// If Settings is null, we can't verify any experiments are in force.
if (!GetFrame()->GetSettings())
return priority;
// If enabled, drop the priority of all resources in a subframe.
if (GetFrame()->GetSettings()->GetLowPriorityIframes() &&
!GetFrame()->IsMainFrame())
return kResourceLoadPriorityVeryLow;
return priority;
}
RefPtr<WebTaskRunner> FrameFetchContext::LoadingTaskRunner() const {
return GetFrame()->FrameScheduler()->LoadingTaskRunner();
}
DEFINE_TRACE(FrameFetchContext) {
visitor->Trace(document_loader_);
BaseFetchContext::Trace(visitor);
}
} // namespace blink