blob: cf94f623ea40c9837b1466ac5b288fc00d4e85d5 [file] [log] [blame]
/*
Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
Copyright (C) 2001 Dirk Mueller (mueller@kde.org)
Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All
rights reserved.
Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
This class provides all functionality needed for loading images, style
sheets and html pages from the web. It has a memory cache for these objects.
*/
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include <algorithm>
#include <limits>
#include <utility>
#include "base/time/time.h"
#include "third_party/blink/public/common/mime_util/mime_util.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/public/platform/interface_provider.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/scheduler/web_scoped_virtual_time_pauser.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/instance_counters.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
#include "third_party/blink/renderer/platform/loader/cors/cors.h"
#include "third_party/blink/renderer/platform/loader/fetch/console_logger.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object_snapshot.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_context.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_utils.h"
#include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h"
#include "third_party/blink/renderer/platform/loader/fetch/raw_resource.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_load_observer.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loading_log.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h"
#include "third_party/blink/renderer/platform/loader/fetch/stale_revalidation_resource_client.h"
#include "third_party/blink/renderer/platform/loader/fetch/unique_identifier.h"
#include "third_party/blink/renderer/platform/mhtml/archive_resource.h"
#include "third_party/blink/renderer/platform/mhtml/mhtml_archive.h"
#include "third_party/blink/renderer/platform/network/encoded_form_data.h"
#include "third_party/blink/renderer/platform/network/network_utils.h"
#include "third_party/blink/renderer/platform/probe/platform_probes.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/weborigin/known_ports.h"
#include "third_party/blink/renderer/platform/weborigin/origin_access_entry.h"
#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/weborigin/security_policy.h"
#include "third_party/blink/renderer/platform/weborigin/security_violation_reporting_policy.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/text/cstring.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/wtf.h"
using blink::WebURLRequest;
namespace blink {
constexpr uint32_t ResourceFetcher::kKeepaliveInflightBytesQuota;
namespace {
constexpr base::TimeDelta kKeepaliveLoadersTimeout =
base::TimeDelta::FromSeconds(30);
#define DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, name) \
case ResourceType::k##name: { \
DEFINE_THREAD_SAFE_STATIC_LOCAL( \
EnumerationHistogram, _single_resource_histogram, \
("Blink.MemoryCache.RevalidationPolicy." prefix #name, kLoad + 1)); \
_single_resource_histogram.Count(policy); \
break; \
}
#define DEFINE_RESOURCE_HISTOGRAM(prefix) \
switch (factory.GetType()) { \
DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, CSSStyleSheet) \
DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Font) \
DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Image) \
DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, ImportResource) \
DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, LinkPrefetch) \
DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Manifest) \
DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Audio) \
DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Video) \
DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Mock) \
DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Raw) \
DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Script) \
DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, SVGDocument) \
DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, TextTrack) \
DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, XSLStyleSheet) \
}
ResourceLoadPriority TypeToPriority(ResourceType type) {
switch (type) {
case ResourceType::kCSSStyleSheet:
case ResourceType::kFont:
// Also parser-blocking scripts (set explicitly in loadPriority)
return ResourceLoadPriority::kVeryHigh;
case ResourceType::kXSLStyleSheet:
DCHECK(RuntimeEnabledFeatures::XSLTEnabled());
FALLTHROUGH;
case ResourceType::kRaw:
case ResourceType::kImportResource:
case ResourceType::kScript:
// Also visible resources/images (set explicitly in loadPriority)
return ResourceLoadPriority::kHigh;
case ResourceType::kManifest:
case ResourceType::kMock:
// Also late-body scripts discovered by the preload scanner (set
// explicitly in loadPriority)
return ResourceLoadPriority::kMedium;
case ResourceType::kImage:
case ResourceType::kTextTrack:
case ResourceType::kAudio:
case ResourceType::kVideo:
case ResourceType::kSVGDocument:
// Also async scripts (set explicitly in loadPriority)
return ResourceLoadPriority::kLow;
case ResourceType::kLinkPrefetch:
return ResourceLoadPriority::kVeryLow;
}
NOTREACHED();
return ResourceLoadPriority::kUnresolved;
}
static bool IsCacheableHTTPMethod(const AtomicString& method) {
// Per http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.10,
// these methods always invalidate the cache entry.
return method != http_names::kPOST && method != http_names::kPUT &&
method != "DELETE";
}
bool ShouldResourceBeAddedToMemoryCache(const FetchParameters& params,
Resource* resource) {
if (!IsMainThread())
return false;
if (params.Options().data_buffering_policy == kDoNotBufferData)
return false;
if (IsRawResource(*resource))
return false;
if (!IsCacheableHTTPMethod(params.GetResourceRequest().HttpMethod()))
return false;
return true;
}
static ResourceFetcher::ResourceFetcherSet& MainThreadFetchersSet() {
DEFINE_STATIC_LOCAL(
Persistent<ResourceFetcher::ResourceFetcherSet>, fetchers,
(MakeGarbageCollected<ResourceFetcher::ResourceFetcherSet>()));
return *fetchers;
}
ResourceLoadPriority AdjustPriorityWithPriorityHint(
ResourceLoadPriority priority_so_far,
ResourceType type,
const ResourceRequest& resource_request,
FetchParameters::DeferOption defer_option,
bool is_link_preload) {
mojom::FetchImportanceMode importance_mode =
resource_request.GetFetchImportanceMode();
ResourceLoadPriority new_priority = priority_so_far;
switch (importance_mode) {
case mojom::FetchImportanceMode::kImportanceAuto:
break;
case mojom::FetchImportanceMode::kImportanceHigh:
// Boost priority of
// - Late and async scripts
// - Images
// - Prefetch
if ((type == ResourceType::kScript &&
(FetchParameters::kLazyLoad == defer_option)) ||
type == ResourceType::kImage || type == ResourceType::kLinkPrefetch) {
new_priority = ResourceLoadPriority::kHigh;
}
DCHECK_LE(priority_so_far, new_priority);
break;
case mojom::FetchImportanceMode::kImportanceLow:
// Demote priority of:
// - Images
// Note: this will only have a real effect on in-viewport images since
// out-of-viewport images already have priority set to kLow
// - Link preloads
// For this initial implementation we do a blanket demotion regardless
// of `as` value/type. TODO(domfarolino): maybe discuss a more
// granular approach with loading team
if (type == ResourceType::kImage ||
resource_request.GetRequestContext() ==
mojom::RequestContextType::FETCH ||
is_link_preload) {
new_priority = ResourceLoadPriority::kLow;
}
DCHECK_LE(new_priority, priority_so_far);
break;
}
return new_priority;
}
std::unique_ptr<TracedValue> BeginResourceLoadData(
const blink::ResourceRequest& request) {
auto value = std::make_unique<TracedValue>();
value->SetString("url", request.Url().GetString());
return value;
}
std::unique_ptr<TracedValue> EndResourceLoadFailData() {
auto value = std::make_unique<TracedValue>();
value->SetString("outcome", "Fail");
return value;
}
std::unique_ptr<TracedValue> ResourcePrioritySetData(
blink::ResourceLoadPriority priority) {
auto value = std::make_unique<TracedValue>();
value->SetInteger("priority", static_cast<int>(priority));
return value;
}
void SetReferrer(
ResourceRequest& request,
const FetchClientSettingsObject& fetch_client_settings_object) {
// TODO(domfarolino): we can probably *just set* the HTTP `Referer` here
// no matter what now.
if (!request.DidSetHttpReferrer()) {
String referrer_to_use = request.ReferrerString();
network::mojom::ReferrerPolicy referrer_policy_to_use =
request.GetReferrerPolicy();
if (referrer_to_use == Referrer::ClientReferrerString())
referrer_to_use = fetch_client_settings_object.GetOutgoingReferrer();
if (referrer_policy_to_use == network::mojom::ReferrerPolicy::kDefault) {
referrer_policy_to_use = fetch_client_settings_object.GetReferrerPolicy();
}
// TODO(domfarolino): Stop storing ResourceRequest's referrer as a header
// and store it elsewhere. See https://crbug.com/850813.
request.SetHttpReferrer(SecurityPolicy::GenerateReferrer(
referrer_policy_to_use, request.Url(), referrer_to_use));
} else {
CHECK_EQ(
SecurityPolicy::GenerateReferrer(request.GetReferrerPolicy(),
request.Url(), request.HttpReferrer())
.referrer,
request.HttpReferrer());
}
}
// This maps the network::mojom::FetchRequestMode to a string that can be used
// in a `Sec-Fetch-Mode` header.
const char* FetchRequestModeToString(network::mojom::FetchRequestMode mode) {
switch (mode) {
case network::mojom::FetchRequestMode::kSameOrigin:
return "same-origin";
case network::mojom::FetchRequestMode::kNoCors:
return "no-cors";
case network::mojom::FetchRequestMode::kCors:
case network::mojom::FetchRequestMode::kCorsWithForcedPreflight:
return "cors";
case network::mojom::FetchRequestMode::kNavigate:
return "navigate";
}
NOTREACHED();
return "";
}
void SetSecFetchHeaders(
ResourceRequest& request,
const FetchClientSettingsObject& fetch_client_settings_object) {
scoped_refptr<SecurityOrigin> url_origin =
SecurityOrigin::Create(request.Url());
if (blink::RuntimeEnabledFeatures::FetchMetadataEnabled() &&
url_origin->IsPotentiallyTrustworthy()) {
const char* destination_value =
FetchUtils::GetDestinationFromContext(request.GetRequestContext());
// If the request's destination is the empty string (e.g. `fetch()`), then
// we'll use the identifier "empty" instead.
if (strlen(destination_value) == 0)
destination_value = "empty";
// We'll handle adding these headers to navigations outside of Blink.
if (strncmp(destination_value, "document", 8) != 0 &&
request.GetRequestContext() != mojom::RequestContextType::INTERNAL) {
if (blink::RuntimeEnabledFeatures::FetchMetadataDestinationEnabled()) {
request.SetHttpHeaderField("Sec-Fetch-Dest", destination_value);
}
request.SetHttpHeaderField(
"Sec-Fetch-Mode",
FetchRequestModeToString(request.GetFetchRequestMode()));
// Note that the `Sec-Fetch-User` header is always false (and therefore
// omitted) for subresource requests. Likewise, note that we rely on
// Blink's embedder to set `Sec-Fetch-Site`, as we don't want to trust the
// renderer to assert its own origin.
}
}
}
} // namespace
ResourceFetcherInit::ResourceFetcherInit(
DetachableResourceFetcherProperties& properties,
FetchContext* context,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
ResourceFetcher::LoaderFactory* loader_factory)
: properties(properties),
context(context),
task_runner(std::move(task_runner)),
loader_factory(loader_factory) {
DCHECK(context);
DCHECK(this->task_runner);
DCHECK(loader_factory || properties.IsDetached());
}
mojom::RequestContextType ResourceFetcher::DetermineRequestContext(
ResourceType type,
IsImageSet is_image_set) {
DCHECK((is_image_set == kImageNotImageSet) ||
(type == ResourceType::kImage && is_image_set == kImageIsImageSet));
switch (type) {
case ResourceType::kXSLStyleSheet:
DCHECK(RuntimeEnabledFeatures::XSLTEnabled());
FALLTHROUGH;
case ResourceType::kCSSStyleSheet:
return mojom::RequestContextType::STYLE;
case ResourceType::kScript:
return mojom::RequestContextType::SCRIPT;
case ResourceType::kFont:
return mojom::RequestContextType::FONT;
case ResourceType::kImage:
if (is_image_set == kImageIsImageSet)
return mojom::RequestContextType::IMAGE_SET;
return mojom::RequestContextType::IMAGE;
case ResourceType::kRaw:
return mojom::RequestContextType::SUBRESOURCE;
case ResourceType::kImportResource:
return mojom::RequestContextType::IMPORT;
case ResourceType::kLinkPrefetch:
return mojom::RequestContextType::PREFETCH;
case ResourceType::kTextTrack:
return mojom::RequestContextType::TRACK;
case ResourceType::kSVGDocument:
return mojom::RequestContextType::IMAGE;
case ResourceType::kAudio:
return mojom::RequestContextType::AUDIO;
case ResourceType::kVideo:
return mojom::RequestContextType::VIDEO;
case ResourceType::kManifest:
return mojom::RequestContextType::MANIFEST;
case ResourceType::kMock:
return mojom::RequestContextType::SUBRESOURCE;
}
NOTREACHED();
return mojom::RequestContextType::SUBRESOURCE;
}
ResourceLoadPriority ResourceFetcher::ComputeLoadPriority(
ResourceType type,
const ResourceRequest& resource_request,
ResourcePriority::VisibilityStatus visibility,
FetchParameters::DeferOption defer_option,
FetchParameters::SpeculativePreloadType speculative_preload_type,
bool is_link_preload,
bool is_stale_revalidation) {
// Stale revalidation resource requests should be very low regardless of
// the |type|.
if (is_stale_revalidation)
return ResourceLoadPriority::kVeryLow;
ResourceLoadPriority priority = TypeToPriority(type);
// Visible resources (images in practice) get a boost to High priority.
if (visibility == ResourcePriority::kVisible)
priority = ResourceLoadPriority::kHigh;
// Resources before the first image are considered "early" in the document and
// resources after the first image are "late" in the document. Important to
// note that this is based on when the preload scanner discovers a resource
// for the most part so the main parser may not have reached the image element
// yet.
if (type == ResourceType::kImage && !is_link_preload)
image_fetched_ = true;
// A preloaded font should not take precedence over critical CSS or
// parser-blocking scripts.
if (type == ResourceType::kFont && is_link_preload)
priority = ResourceLoadPriority::kHigh;
if (FetchParameters::kIdleLoad == defer_option) {
priority = ResourceLoadPriority::kVeryLow;
} else if (type == ResourceType::kScript) {
// Special handling for scripts.
// Default/Parser-Blocking/Preload early in document: High (set in
// typeToPriority)
// Async/Defer: Low Priority (applies to both preload and parser-inserted)
// Preload late in document: Medium
if (FetchParameters::kLazyLoad == defer_option) {
priority = ResourceLoadPriority::kLow;
} else if (speculative_preload_type ==
FetchParameters::SpeculativePreloadType::kInDocument &&
image_fetched_) {
// Speculative preload is used as a signal for scripts at the bottom of
// the document.
priority = ResourceLoadPriority::kMedium;
}
} else if (FetchParameters::kLazyLoad == defer_option) {
priority = ResourceLoadPriority::kVeryLow;
} else if (resource_request.GetRequestContext() ==
mojom::RequestContextType::BEACON ||
resource_request.GetRequestContext() ==
mojom::RequestContextType::PING ||
resource_request.GetRequestContext() ==
mojom::RequestContextType::CSP_REPORT) {
priority = ResourceLoadPriority::kVeryLow;
}
priority = AdjustPriorityWithPriorityHint(priority, type, resource_request,
defer_option, is_link_preload);
if (properties_->IsSubframeDeprioritizationEnabled()) {
if (properties_->IsMainFrame()) {
DEFINE_STATIC_LOCAL(
EnumerationHistogram, main_frame_priority_histogram,
("LowPriorityIframes.MainFrameRequestPriority",
static_cast<int>(ResourceLoadPriority::kHighest) + 1));
main_frame_priority_histogram.Count(static_cast<int>(priority));
} else {
DEFINE_STATIC_LOCAL(
EnumerationHistogram, iframe_priority_histogram,
("LowPriorityIframes.IframeRequestPriority",
static_cast<int>(ResourceLoadPriority::kHighest) + 1));
iframe_priority_histogram.Count(static_cast<int>(priority));
// When enabled, the priority of all resources in subframe is dropped.
// Non-delayable resources are assigned a priority of kLow, and the rest
// of them are assigned a priority of kLowest. This ensures that if the
// webpage fetches most of its primary content using iframes, then high
// priority requests within the iframe go on the network first.
if (priority >= ResourceLoadPriority::kHigh) {
priority = ResourceLoadPriority::kLow;
} else {
priority = ResourceLoadPriority::kLowest;
}
}
}
// A manually set priority acts as a floor. This is used to ensure that
// synchronous requests are always given the highest possible priority, as
// well as to ensure that there isn't priority churn if images move in and out
// of the viewport, or are displayed more than once, both in and out of the
// viewport.
return std::max(resource_request.Priority(), priority);
}
ResourceFetcher::ResourceFetcher(const ResourceFetcherInit& init)
: properties_(*init.properties),
context_(init.context),
task_runner_(init.task_runner),
console_logger_(init.console_logger
? init.console_logger.Get()
: MakeGarbageCollected<DetachableConsoleLogger>()),
loader_factory_(init.loader_factory),
scheduler_(MakeGarbageCollected<ResourceLoadScheduler>(
init.initial_throttling_policy,
*properties_,
init.frame_scheduler,
*console_logger_)),
archive_(init.archive),
resource_timing_report_timer_(
task_runner_,
this,
&ResourceFetcher::ResourceTimingReportTimerFired),
frame_scheduler_(init.frame_scheduler ? init.frame_scheduler->GetWeakPtr()
: nullptr),
auto_load_images_(true),
images_enabled_(true),
allow_stale_resources_(false),
image_fetched_(false) {
stale_while_revalidate_enabled_ =
RuntimeEnabledFeatures::StaleWhileRevalidateEnabledByRuntimeFlag();
InstanceCounters::IncrementCounter(InstanceCounters::kResourceFetcherCounter);
if (IsMainThread())
MainThreadFetchersSet().insert(this);
}
ResourceFetcher::~ResourceFetcher() {
InstanceCounters::DecrementCounter(InstanceCounters::kResourceFetcherCounter);
}
bool ResourceFetcher::IsDetached() const {
return properties_->IsDetached();
}
Resource* ResourceFetcher::CachedResource(const KURL& resource_url) const {
if (resource_url.IsEmpty())
return nullptr;
KURL url = MemoryCache::RemoveFragmentIdentifierIfNeeded(resource_url);
const WeakMember<Resource>& resource = cached_resources_map_.at(url);
return resource.Get();
}
mojom::ControllerServiceWorkerMode
ResourceFetcher::IsControlledByServiceWorker() const {
return properties_->GetControllerServiceWorkerMode();
}
bool ResourceFetcher::ResourceNeedsLoad(Resource* resource,
const FetchParameters& params,
RevalidationPolicy policy) {
// Defer a font load until it is actually needed unless this is a link
// preload.
if (resource->GetType() == ResourceType::kFont && !params.IsLinkPreload())
return false;
// Defer loading images either when:
// - images are disabled
// - instructed to defer loading images from network
if (resource->GetType() == ResourceType::kImage &&
(ShouldDeferImageLoad(resource->Url()) ||
params.GetImageRequestOptimization() ==
FetchParameters::kDeferImageLoad)) {
return false;
}
return policy != kUse || resource->StillNeedsLoad();
}
void ResourceFetcher::RequestLoadStarted(uint64_t identifier,
Resource* resource,
const FetchParameters& params,
RevalidationPolicy policy,
bool is_static_data) {
KURL url = MemoryCache::RemoveFragmentIdentifierIfNeeded(params.Url());
if (policy == kUse && resource->GetStatus() == ResourceStatus::kCached &&
!cached_resources_map_.Contains(url)) {
// Loaded from MemoryCache.
DidLoadResourceFromMemoryCache(identifier, resource,
params.GetResourceRequest());
}
if (is_static_data)
return;
if (policy == kUse && !resource->StillNeedsLoad() &&
!cached_resources_map_.Contains(url)) {
// Resources loaded from memory cache should be reported the first time
// they're used.
scoped_refptr<ResourceTimingInfo> info = ResourceTimingInfo::Create(
params.Options().initiator_info.name, CurrentTimeTicks());
// TODO(yoav): GetInitialUrlForResourceTiming() is only needed until
// Out-of-Blink CORS lands: https://crbug.com/736308
info->SetInitialURL(
resource->GetResourceRequest().GetInitialUrlForResourceTiming().IsNull()
? resource->GetResourceRequest().Url()
: resource->GetResourceRequest().GetInitialUrlForResourceTiming());
ResourceResponse final_response = resource->GetResponse();
final_response.SetResourceLoadTiming(nullptr);
info->SetFinalResponse(final_response);
info->SetLoadResponseEnd(info->InitialTime());
scheduled_resource_timing_reports_.push_back(std::move(info));
if (!resource_timing_report_timer_.IsActive())
resource_timing_report_timer_.StartOneShot(TimeDelta(), FROM_HERE);
}
}
void ResourceFetcher::DidLoadResourceFromMemoryCache(
uint64_t identifier,
Resource* resource,
const ResourceRequest& request) {
if (IsDetached() || !resource_load_observer_)
return;
resource_load_observer_->WillSendRequest(
identifier, request, ResourceResponse() /* redirects */,
resource->GetType(), resource->Options().initiator_info);
resource_load_observer_->DidReceiveResponse(
identifier, request, resource->GetResponse(), resource,
ResourceLoadObserver::ResponseSource::kFromMemoryCache);
if (resource->EncodedSize() > 0) {
resource_load_observer_->DidReceiveData(
identifier, base::span<const char>(nullptr, resource->EncodedSize()));
}
resource_load_observer_->DidFinishLoading(
identifier, TimeTicks(), 0, resource->GetResponse().DecodedBodyLength(),
false, ResourceLoadObserver::ResponseSource::kFromMemoryCache);
}
Resource* ResourceFetcher::ResourceForStaticData(
const FetchParameters& params,
const ResourceFactory& factory) {
const KURL& url = params.GetResourceRequest().Url();
DCHECK(url.ProtocolIsData() || archive_);
if (!archive_ && factory.GetType() == ResourceType::kRaw)
return nullptr;
const String cache_identifier = GetCacheIdentifier();
// Most off-main-thread resource fetches use Resource::kRaw and don't reach
// this point, but off-main-thread module fetches might.
if (IsMainThread()) {
if (Resource* old_resource =
GetMemoryCache()->ResourceForURL(url, cache_identifier)) {
// There's no reason to re-parse if we saved the data from the previous
// parse.
if (params.Options().data_buffering_policy != kDoNotBufferData)
return old_resource;
GetMemoryCache()->Remove(old_resource);
}
}
ResourceResponse response;
scoped_refptr<SharedBuffer> data;
if (url.ProtocolIsData()) {
int result;
std::tie(result, response, data) = network_utils::ParseDataURL(url);
if (result != net::OK) {
return nullptr;
}
// TODO(yhirano): Consider removing this.
if (!IsSupportedMimeType(WebString(response.MimeType()).Utf8())) {
return nullptr;
}
} else {
ArchiveResource* archive_resource =
archive_->SubresourceForURL(params.Url());
// The archive doesn't contain the resource, the request must be aborted.
if (!archive_resource)
return nullptr;
data = archive_resource->Data();
response.SetCurrentRequestUrl(url);
response.SetMimeType(archive_resource->MimeType());
response.SetExpectedContentLength(data->size());
response.SetTextEncodingName(archive_resource->TextEncoding());
}
Resource* resource = factory.Create(
params.GetResourceRequest(), params.Options(), params.DecoderOptions());
resource->NotifyStartLoad();
// FIXME: We should provide a body stream here.
resource->ResponseReceived(response);
resource->SetDataBufferingPolicy(kBufferData);
if (data->size())
resource->SetResourceBuffer(data);
resource->SetCacheIdentifier(cache_identifier);
resource->Finish(TimeTicks(), task_runner_.get());
AddToMemoryCacheIfNeeded(params, resource);
return resource;
}
Resource* ResourceFetcher::ResourceForBlockedRequest(
const FetchParameters& params,
const ResourceFactory& factory,
ResourceRequestBlockedReason blocked_reason,
ResourceClient* client) {
Resource* resource = factory.Create(
params.GetResourceRequest(), params.Options(), params.DecoderOptions());
if (client)
client->SetResource(resource, task_runner_.get());
resource->FinishAsError(ResourceError::CancelledDueToAccessCheckError(
params.Url(), blocked_reason),
task_runner_.get());
return resource;
}
void ResourceFetcher::MakePreloadedResourceBlockOnloadIfNeeded(
Resource* resource,
const FetchParameters& params) {
// TODO(yoav): Test that non-blocking resources (video/audio/track) continue
// to not-block even after being preloaded and discovered.
if (resource && resource->Loader() &&
resource->IsLoadEventBlockingResourceType() &&
resource->IsLinkPreload() && !params.IsLinkPreload() &&
non_blocking_loaders_.Contains(resource->Loader())) {
non_blocking_loaders_.erase(resource->Loader());
loaders_.insert(resource->Loader());
}
}
void ResourceFetcher::UpdateMemoryCacheStats(Resource* resource,
RevalidationPolicy policy,
const FetchParameters& params,
const ResourceFactory& factory,
bool is_static_data) const {
if (is_static_data)
return;
DEFINE_THREAD_SAFE_STATIC_LOCAL(
BooleanHistogram, resource_histogram,
("Blink.ResourceFetcher.StaleWhileRevalidate"));
resource_histogram.Count(params.IsStaleRevalidation());
if (params.IsSpeculativePreload() || params.IsLinkPreload()) {
DEFINE_RESOURCE_HISTOGRAM("Preload.");
} else {
DEFINE_RESOURCE_HISTOGRAM("");
}
// Aims to count Resource only referenced from MemoryCache (i.e. what would be
// dead if MemoryCache holds weak references to Resource). Currently we check
// references to Resource from ResourceClient and |m_preloads| only, because
// they are major sources of references.
if (resource && !resource->IsAlive() && !ContainsAsPreload(resource)) {
DEFINE_RESOURCE_HISTOGRAM("Dead.");
}
}
bool ResourceFetcher::ContainsAsPreload(Resource* resource) const {
auto it = preloads_.find(PreloadKey(resource->Url(), resource->GetType()));
return it != preloads_.end() && it->value == resource;
}
void ResourceFetcher::RemovePreload(Resource* resource) {
auto it = preloads_.find(PreloadKey(resource->Url(), resource->GetType()));
if (it == preloads_.end())
return;
if (it->value == resource)
preloads_.erase(it);
}
base::Optional<ResourceRequestBlockedReason> ResourceFetcher::PrepareRequest(
FetchParameters& params,
const ResourceFactory& factory,
uint64_t identifier,
WebScopedVirtualTimePauser& virtual_time_pauser) {
ResourceRequest& resource_request = params.MutableResourceRequest();
ResourceType resource_type = factory.GetType();
const ResourceLoaderOptions& options = params.Options();
DCHECK(options.synchronous_policy == kRequestAsynchronously ||
resource_type == ResourceType::kRaw ||
resource_type == ResourceType::kXSLStyleSheet);
params.OverrideContentType(factory.ContentType());
// Don't send security violation reports for speculative preloads.
SecurityViolationReportingPolicy reporting_policy =
params.IsSpeculativePreload()
? SecurityViolationReportingPolicy::kSuppressReporting
: SecurityViolationReportingPolicy::kReport;
// Note that resource_request.GetRedirectStatus() may return kFollowedRedirect
// here since e.g. ThreadableLoader may create a new Resource from
// a ResourceRequest that originates from the ResourceRequest passed to
// the redirect handling callback.
// 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).
Context().CheckCSPForRequest(
resource_request.GetRequestContext(),
MemoryCache::RemoveFragmentIdentifierIfNeeded(params.Url()), options,
reporting_policy, resource_request.GetRedirectStatus());
// This may modify params.Url() (via the resource_request argument).
Context().PopulateResourceRequest(
resource_type, params.GetClientHintsPreferences(),
params.GetResourceWidth(), resource_request);
if (!params.Url().IsValid())
return ResourceRequestBlockedReason::kOther;
resource_request.SetPriority(ComputeLoadPriority(
resource_type, params.GetResourceRequest(), ResourcePriority::kNotVisible,
params.Defer(), params.GetSpeculativePreloadType(),
params.IsLinkPreload(), params.IsStaleRevalidation()));
if (resource_request.GetCacheMode() == mojom::FetchCacheMode::kDefault) {
resource_request.SetCacheMode(Context().ResourceRequestCachePolicy(
resource_request, resource_type, params.Defer()));
}
if (resource_request.GetRequestContext() ==
mojom::RequestContextType::UNSPECIFIED) {
resource_request.SetRequestContext(
DetermineRequestContext(resource_type, kImageNotImageSet));
}
if (resource_type == ResourceType::kLinkPrefetch)
resource_request.SetPurposeHeader("prefetch");
// Indicate whether the network stack can return a stale resource. If a
// stale resource is returned a StaleRevalidation request will be scheduled.
// Explicitly disallow stale responses for fetchers that don't have SWR
// enabled (via origin trial), and non-GET requests.
resource_request.SetAllowStaleResponse(stale_while_revalidate_enabled_ &&
resource_request.HttpMethod() ==
http_names::kGET &&
!params.IsStaleRevalidation());
SetReferrer(resource_request, properties_->GetFetchClientSettingsObject());
resource_request.SetExternalRequestStateFromRequestorAddressSpace(
properties_->GetFetchClientSettingsObject().GetAddressSpace());
SetSecFetchHeaders(resource_request,
properties_->GetFetchClientSettingsObject());
Context().AddAdditionalRequestHeaders(resource_request);
TRACE_EVENT_NESTABLE_ASYNC_INSTANT1(
TRACE_DISABLED_BY_DEFAULT("network"), "ResourcePrioritySet",
TRACE_ID_WITH_SCOPE("BlinkResourceID", TRACE_ID_LOCAL(identifier)),
"data", ResourcePrioritySetData(resource_request.Priority()));
KURL url = MemoryCache::RemoveFragmentIdentifierIfNeeded(params.Url());
base::Optional<ResourceRequestBlockedReason> blocked_reason =
Context().CanRequest(resource_type, resource_request, url, options,
reporting_policy,
resource_request.GetRedirectStatus());
if (Context().CalculateIfAdSubresource(resource_request, resource_type))
resource_request.SetIsAdResource();
if (blocked_reason)
return blocked_reason;
// For initial requests, call PrepareRequest() here before revalidation
// policy is determined.
Context().PrepareRequest(resource_request, options.initiator_info,
virtual_time_pauser, resource_type);
if (!params.Url().IsValid())
return ResourceRequestBlockedReason::kOther;
if (!RuntimeEnabledFeatures::OutOfBlinkCorsEnabled() &&
options.cors_handling_by_resource_fetcher ==
kEnableCorsHandlingByResourceFetcher) {
const scoped_refptr<const SecurityOrigin> origin =
resource_request.RequestorOrigin();
DCHECK(!options.cors_flag);
params.MutableOptions().cors_flag = cors::CalculateCorsFlag(
params.Url(), origin.get(), resource_request.GetFetchRequestMode());
// TODO(yhirano): Reject requests for non CORS-enabled schemes.
// See https://crrev.com/c/1298828.
resource_request.SetAllowStoredCredentials(cors::CalculateCredentialsFlag(
resource_request.GetFetchCredentialsMode(),
cors::CalculateResponseTainting(
params.Url(), resource_request.GetFetchRequestMode(), origin.get(),
params.Options().cors_flag ? CorsFlag::Set : CorsFlag::Unset)));
}
if (RuntimeEnabledFeatures::OutOfBlinkCorsEnabled() &&
resource_request.GetFetchCredentialsMode() ==
network::mojom::FetchCredentialsMode::kOmit) {
// See comments at network::ResourceRequest::fetch_credentials_mode.
resource_request.SetAllowStoredCredentials(false);
}
return base::nullopt;
}
Resource* ResourceFetcher::RequestResource(FetchParameters& params,
const ResourceFactory& factory,
ResourceClient* client) {
// If detached, we do very early return here to skip all processing below.
if (properties_->IsDetached()) {
return ResourceForBlockedRequest(
params, factory, ResourceRequestBlockedReason::kOther, client);
}
// Otherwise, we assume we can send network requests and the fetch client's
// settings object's origin is non-null.
DCHECK(properties_->GetFetchClientSettingsObject().GetSecurityOrigin());
uint64_t identifier = CreateUniqueIdentifier();
ResourceRequest& resource_request = params.MutableResourceRequest();
resource_request.SetInspectorId(identifier);
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
TRACE_DISABLED_BY_DEFAULT("network"), "ResourceLoad",
TRACE_ID_WITH_SCOPE("BlinkResourceID", TRACE_ID_LOCAL(identifier)),
"beginData", BeginResourceLoadData(resource_request));
SCOPED_BLINK_UMA_HISTOGRAM_TIMER_THREAD_SAFE(
"Blink.Fetch.RequestResourceTime");
TRACE_EVENT1("blink", "ResourceFetcher::requestResource", "url",
params.Url().GetString().Utf8());
// TODO(crbug.com/123004): Remove once we have enough stats on data URIs that
// contain fragments ('#' characters).
//
// TODO(crbug.com/796173): This call happens before commit for iframes that
// have data URI sources, which causes UKM to miss the metric recording.
if (context_) {
const KURL& url = params.Url();
if (url.HasFragmentIdentifier() && url.ProtocolIsData()) {
context_->CountUsage(mojom::WebFeature::kDataUriHasOctothorpe);
}
}
// We need to attach an origin header when the request's method is neither
// GET nor HEAD. For requests made by an extension content scripts, we want to
// attach page's origin, whereas the request's origin is the content script's
// origin. See https://crbug.com/944704 for details.
// TODO(crbug.com/940068) Remove this.
if (resource_request.HttpMethod() != http_names::kGET &&
resource_request.HttpMethod() != http_names::kHEAD &&
resource_request.RequestorOrigin() &&
!resource_request.RequestorOrigin()->IsSameSchemeHostPort(
properties_->GetFetchClientSettingsObject().GetSecurityOrigin())) {
resource_request.SetHttpOriginIfNeeded(
properties_->GetFetchClientSettingsObject().GetSecurityOrigin());
}
// |resource_request|'s origin can be null here, corresponding to the "client"
// value in the spec. In that case client's origin is used.
if (!resource_request.RequestorOrigin()) {
resource_request.SetRequestorOrigin(
properties_->GetFetchClientSettingsObject().GetSecurityOrigin());
}
const ResourceType resource_type = factory.GetType();
if (!RuntimeEnabledFeatures::OutOfBlinkCorsEnabled() &&
resource_request.RequestorOrigin()) {
resource_request.SetHttpOriginIfNeeded(
resource_request.RequestorOrigin().get());
}
WebScopedVirtualTimePauser pauser;
base::Optional<ResourceRequestBlockedReason> blocked_reason =
PrepareRequest(params, factory, identifier, pauser);
if (blocked_reason) {
return ResourceForBlockedRequest(params, factory, blocked_reason.value(),
client);
}
if (!params.IsSpeculativePreload()) {
// Only log if it's not for speculative preload.
Context().RecordLoadingActivity(resource_request, resource_type,
params.Options().initiator_info.name);
}
Resource* resource = nullptr;
RevalidationPolicy policy = kLoad;
bool is_data_url = resource_request.Url().ProtocolIsData();
bool is_static_data = is_data_url || archive_;
bool is_stale_revalidation = params.IsStaleRevalidation();
if (!is_stale_revalidation && is_static_data) {
resource = ResourceForStaticData(params, factory);
if (resource) {
policy =
DetermineRevalidationPolicy(resource_type, params, *resource, true);
} else if (!is_data_url && archive_) {
// Abort the request if the archive doesn't contain the resource, except
// in the case of data URLs which might have resources such as fonts that
// need to be decoded only on demand. These data URLs are allowed to be
// processed using the normal ResourceFetcher machinery.
return ResourceForBlockedRequest(
params, factory, ResourceRequestBlockedReason::kOther, client);
}
}
if (!is_stale_revalidation && !resource) {
resource = MatchPreload(params, resource_type);
if (resource) {
policy = kUse;
// If |params| is for a blocking resource and a preloaded resource is
// found, we may need to make it block the onload event.
MakePreloadedResourceBlockOnloadIfNeeded(resource, params);
} else if (IsMainThread()) {
resource =
GetMemoryCache()->ResourceForURL(params.Url(), GetCacheIdentifier());
if (resource) {
policy = DetermineRevalidationPolicy(resource_type, params, *resource,
is_static_data);
}
}
}
UpdateMemoryCacheStats(resource, policy, params, factory, is_static_data);
switch (policy) {
case kReload:
GetMemoryCache()->Remove(resource);
FALLTHROUGH;
case kLoad:
resource = CreateResourceForLoading(params, factory);
break;
case kRevalidate:
InitializeRevalidation(resource_request, resource);
break;
case kUse:
if (resource_request.AllowsStaleResponse() &&
resource->ShouldRevalidateStaleResponse()) {
ScheduleStaleRevalidate(resource);
}
if (resource->IsLinkPreload() && !params.IsLinkPreload())
resource->SetLinkPreload(false);
break;
}
DCHECK(resource);
// TODO(yoav): turn to a DCHECK. See https://crbug.com/690632
CHECK_EQ(resource->GetType(), resource_type);
if (policy != kUse)
resource->VirtualTimePauser() = std::move(pauser);
if (client)
client->SetResource(resource, task_runner_.get());
// TODO(yoav): It is not clear why preloads are exempt from this check. Can we
// remove the exemption?
if (!params.IsSpeculativePreload() || policy != kUse) {
// When issuing another request for a resource that is already in-flight
// make sure to not demote the priority of the in-flight request. If the new
// request isn't at the same priority as the in-flight request, only allow
// promotions. This can happen when a visible image's priority is increased
// and then another reference to the image is parsed (which would be at a
// lower priority).
if (resource_request.Priority() > resource->GetResourceRequest().Priority())
resource->DidChangePriority(resource_request.Priority(), 0);
// TODO(yoav): I'd expect the stated scenario to not go here, as its policy
// would be Use.
}
// If only the fragment identifiers differ, it is the same resource.
DCHECK(EqualIgnoringFragmentIdentifier(resource->Url(), params.Url()));
RequestLoadStarted(identifier, resource, params, policy, is_static_data);
if (!is_stale_revalidation) {
cached_resources_map_.Set(
MemoryCache::RemoveFragmentIdentifierIfNeeded(params.Url()), resource);
}
document_resources_.insert(resource);
// Returns with an existing resource if the resource does not need to start
// loading immediately. If revalidation policy was determined as |Revalidate|,
// the resource was already initialized for the revalidation here, but won't
// start loading.
if (ResourceNeedsLoad(resource, params, policy)) {
if (!StartLoad(resource)) {
resource->FinishAsError(ResourceError::CancelledError(params.Url()),
task_runner_.get());
}
}
if (policy != kUse)
InsertAsPreloadIfNecessary(resource, params, resource_type);
if (resource->InspectorId() != identifier ||
(!resource->StillNeedsLoad() && !resource->IsLoading())) {
TRACE_EVENT_NESTABLE_ASYNC_END1(
TRACE_DISABLED_BY_DEFAULT("network"), "ResourceLoad",
TRACE_ID_WITH_SCOPE("BlinkResourceID", TRACE_ID_LOCAL(identifier)),
"endData", EndResourceLoadFailData());
}
return resource;
}
void ResourceFetcher::ResourceTimingReportTimerFired(TimerBase* timer) {
DCHECK_EQ(timer, &resource_timing_report_timer_);
Vector<scoped_refptr<ResourceTimingInfo>> timing_reports;
timing_reports.swap(scheduled_resource_timing_reports_);
for (const auto& timing_info : timing_reports)
Context().AddResourceTiming(*timing_info);
}
void ResourceFetcher::InitializeRevalidation(
ResourceRequest& revalidating_request,
Resource* resource) {
DCHECK(resource);
DCHECK(GetMemoryCache()->Contains(resource));
DCHECK(resource->IsLoaded());
DCHECK(resource->CanUseCacheValidator());
DCHECK(!resource->IsCacheValidator());
DCHECK_EQ(properties_->GetControllerServiceWorkerMode(),
mojom::ControllerServiceWorkerMode::kNoController);
// RawResource doesn't support revalidation.
CHECK(!IsRawResource(*resource));
revalidating_request.SetIsRevalidating(true);
const AtomicString& last_modified =
resource->GetResponse().HttpHeaderField(http_names::kLastModified);
const AtomicString& e_tag =
resource->GetResponse().HttpHeaderField(http_names::kETag);
if (!last_modified.IsEmpty() || !e_tag.IsEmpty()) {
DCHECK_NE(mojom::FetchCacheMode::kBypassCache,
revalidating_request.GetCacheMode());
if (revalidating_request.GetCacheMode() ==
mojom::FetchCacheMode::kValidateCache) {
revalidating_request.SetHttpHeaderField(http_names::kCacheControl,
"max-age=0");
}
}
if (!last_modified.IsEmpty()) {
revalidating_request.SetHttpHeaderField(http_names::kIfModifiedSince,
last_modified);
}
if (!e_tag.IsEmpty())
revalidating_request.SetHttpHeaderField(http_names::kIfNoneMatch, e_tag);
resource->SetRevalidatingRequest(revalidating_request);
}
std::unique_ptr<WebURLLoader> ResourceFetcher::CreateURLLoader(
const ResourceRequest& request,
const ResourceLoaderOptions& options) {
DCHECK(!GetProperties().IsDetached());
DCHECK(loader_factory_);
return loader_factory_->CreateURLLoader(request, options, task_runner_);
}
std::unique_ptr<CodeCacheLoader> ResourceFetcher::CreateCodeCacheLoader() {
DCHECK(!GetProperties().IsDetached());
DCHECK(loader_factory_);
return loader_factory_->CreateCodeCacheLoader();
}
void ResourceFetcher::AddToMemoryCacheIfNeeded(const FetchParameters& params,
Resource* resource) {
if (!ShouldResourceBeAddedToMemoryCache(params, resource))
return;
GetMemoryCache()->Add(resource);
}
Resource* ResourceFetcher::CreateResourceForLoading(
const FetchParameters& params,
const ResourceFactory& factory) {
const String cache_identifier = GetCacheIdentifier();
DCHECK(!IsMainThread() || params.IsStaleRevalidation() ||
!GetMemoryCache()->ResourceForURL(params.GetResourceRequest().Url(),
cache_identifier));
RESOURCE_LOADING_DVLOG(1) << "Loading Resource for "
<< params.GetResourceRequest().Url().ElidedString();
Resource* resource = factory.Create(
params.GetResourceRequest(), params.Options(), params.DecoderOptions());
resource->SetLinkPreload(params.IsLinkPreload());
resource->SetCacheIdentifier(cache_identifier);
AddToMemoryCacheIfNeeded(params, resource);
return resource;
}
void ResourceFetcher::StorePerformanceTimingInitiatorInformation(
Resource* resource) {
const AtomicString& fetch_initiator = resource->Options().initiator_info.name;
if (fetch_initiator == fetch_initiator_type_names::kInternal)
return;
scoped_refptr<ResourceTimingInfo> info =
ResourceTimingInfo::Create(fetch_initiator, CurrentTimeTicks());
resource_timing_info_map_.insert(resource, std::move(info));
}
void ResourceFetcher::RecordResourceTimingOnRedirect(
Resource* resource,
const ResourceResponse& redirect_response,
const KURL& new_url) {
ResourceTimingInfoMap::iterator it = resource_timing_info_map_.find(resource);
if (it != resource_timing_info_map_.end())
it->value->AddRedirect(redirect_response, new_url);
}
static bool IsDownloadOrStreamRequest(const ResourceRequest& request) {
// Never use cache entries for DownloadToBlob / UseStreamOnResponse requests.
// The data will be delivered through other paths.
return request.DownloadToBlob() || request.UseStreamOnResponse();
}
Resource* ResourceFetcher::MatchPreload(const FetchParameters& params,
ResourceType type) {
auto it = preloads_.find(PreloadKey(params.Url(), type));
if (it == preloads_.end())
return nullptr;
Resource* resource = it->value;
if (resource->MustRefetchDueToIntegrityMetadata(params)) {
if (!params.IsSpeculativePreload() && !params.IsLinkPreload())
PrintPreloadWarning(resource, Resource::MatchStatus::kIntegrityMismatch);
return nullptr;
}
if (params.IsSpeculativePreload())
return resource;
if (params.IsLinkPreload()) {
resource->SetLinkPreload(true);
return resource;
}
const ResourceRequest& request = params.GetResourceRequest();
if (request.DownloadToBlob()) {
PrintPreloadWarning(resource, Resource::MatchStatus::kBlobRequest);
return nullptr;
}
if (IsImageResourceDisallowedToBeReused(*resource)) {
PrintPreloadWarning(resource, Resource::MatchStatus::kImageLoadingDisabled);
return nullptr;
}
const Resource::MatchStatus match_status = resource->CanReuse(params);
if (match_status != Resource::MatchStatus::kOk) {
PrintPreloadWarning(resource, match_status);
return nullptr;
}
if (!resource->MatchPreload(params, task_runner_.get())) {
PrintPreloadWarning(resource, Resource::MatchStatus::kUnknownFailure);
return nullptr;
}
preloads_.erase(it);
matched_preloads_.push_back(resource);
return resource;
}
void ResourceFetcher::PrintPreloadWarning(Resource* resource,
Resource::MatchStatus status) {
if (!resource->IsLinkPreload())
return;
StringBuilder builder;
builder.Append("A preload for '");
builder.Append(resource->Url());
builder.Append("' is found, but is not used ");
switch (status) {
case Resource::MatchStatus::kOk:
NOTREACHED();
break;
case Resource::MatchStatus::kUnknownFailure:
builder.Append("due to an unknown reason.");
break;
case Resource::MatchStatus::kIntegrityMismatch:
builder.Append("due to an integrity mismatch.");
break;
case Resource::MatchStatus::kBlobRequest:
builder.Append("because the new request loads the content as a blob.");
break;
case Resource::MatchStatus::kImageLoadingDisabled:
builder.Append("because image loading is disabled.");
break;
case Resource::MatchStatus::kSynchronousFlagDoesNotMatch:
builder.Append("because the new request is synchronous.");
break;
case Resource::MatchStatus::kRequestModeDoesNotMatch:
builder.Append("because the request mode does not match. ");
builder.Append("Consider taking a look at crossorigin attribute.");
break;
case Resource::MatchStatus::kRequestCredentialsModeDoesNotMatch:
builder.Append("because the request credentials mode does not match. ");
builder.Append("Consider taking a look at crossorigin attribute.");
break;
case Resource::MatchStatus::kKeepaliveSet:
builder.Append("because the keepalive flag is set.");
break;
case Resource::MatchStatus::kRequestMethodDoesNotMatch:
builder.Append("because the request HTTP method does not match.");
break;
case Resource::MatchStatus::kRequestHeadersDoNotMatch:
builder.Append("because the request headers do not match.");
break;
case Resource::MatchStatus::kImagePlaceholder:
builder.Append("due to different image placeholder policies.");
break;
}
console_logger_->AddConsoleMessage(mojom::ConsoleMessageSource::kOther,
mojom::ConsoleMessageLevel::kWarning,
builder.ToString());
}
void ResourceFetcher::InsertAsPreloadIfNecessary(Resource* resource,
const FetchParameters& params,
ResourceType type) {
if (!params.IsSpeculativePreload() && !params.IsLinkPreload())
return;
DCHECK(!params.IsStaleRevalidation());
// CSP web tests verify that preloads are subject to access checks by
// seeing if they are in the `preload started` list. Therefore do not add
// them to the list if the load is immediately denied.
if (resource->LoadFailedOrCanceled() &&
resource->GetResourceError().IsAccessCheck()) {
return;
}
PreloadKey key(params.Url(), type);
if (preloads_.find(key) != preloads_.end())
return;
preloads_.insert(key, resource);
resource->MarkAsPreload();
if (preloaded_urls_for_test_)
preloaded_urls_for_test_->insert(resource->Url().GetString());
}
bool ResourceFetcher::IsImageResourceDisallowedToBeReused(
const Resource& existing_resource) const {
// When images are disabled, don't ever load images, even if the image is
// cached or it is a data: url. In this case:
// - remove the image from the memory cache, and
// - create a new resource but defer loading (this is done by
// ResourceNeedsLoad()).
//
// This condition must be placed before the condition on |is_static_data| to
// prevent loading a data: URL.
//
// TODO(japhet): Can we get rid of one of these settings?
if (existing_resource.GetType() != ResourceType::kImage)
return false;
return !Context().AllowImage(images_enabled_, existing_resource.Url());
}
ResourceFetcher::RevalidationPolicy
ResourceFetcher::DetermineRevalidationPolicy(
ResourceType type,
const FetchParameters& fetch_params,
const Resource& existing_resource,
bool is_static_data) const {
RevalidationPolicy policy;
const char* reason;
std::tie(policy, reason) = DetermineRevalidationPolicyInternal(
type, fetch_params, existing_resource, is_static_data);
DCHECK(reason);
RESOURCE_LOADING_DVLOG(1)
<< "ResourceFetcher::DetermineRevalidationPolicy "
<< "url = " << fetch_params.Url() << ", policy = " << GetNameFor(policy)
<< ", reason = \"" << reason << "\"";
TRACE_EVENT_INSTANT2("blink", "ResourceFetcher::DetermineRevalidationPolicy",
TRACE_EVENT_SCOPE_THREAD, "policy", GetNameFor(policy),
"reason", reason);
return policy;
}
const char* ResourceFetcher::GetNameFor(RevalidationPolicy policy) {
switch (policy) {
case kUse:
return "use";
case kRevalidate:
return "revalidate";
case kReload:
return "reload";
case kLoad:
return "load";
}
NOTREACHED();
}
std::pair<ResourceFetcher::RevalidationPolicy, const char*>
ResourceFetcher::DetermineRevalidationPolicyInternal(
ResourceType type,
const FetchParameters& fetch_params,
const Resource& existing_resource,
bool is_static_data) const {
const ResourceRequest& request = fetch_params.GetResourceRequest();
if (IsDownloadOrStreamRequest(request)) {
return std::make_pair(kReload, "It is for download or for streaming.");
}
if (IsImageResourceDisallowedToBeReused(existing_resource)) {
return std::make_pair(kReload, "Reload due to 'allow image' settings.");
}
// If the existing resource is loading and the associated fetcher is not equal
// to |this|, we must not use the resource. Otherwise, CSP violation may
// happen in redirect handling.
if (existing_resource.Loader() &&
existing_resource.Loader()->Fetcher() != this) {
return std::make_pair(
kReload, "The existing resource is loading in a foreign fetcher.");
}
// It's hard to share a not-yet-referenced preloads via MemoryCache correctly.
// A not-yet-matched preloads made by a foreign ResourceFetcher and stored in
// the memory cache could be used without this block.
if ((fetch_params.IsLinkPreload() || fetch_params.IsSpeculativePreload()) &&
existing_resource.IsUnusedPreload()) {
return std::make_pair(kReload,
"The existing resource is an unused preload made "
"from a foreign fetcher.");
}
// Checks if the resource has an explicit policy about integrity metadata.
//
// This is necessary because ScriptResource and CSSStyleSheetResource objects
// do not keep the raw data around after the source is accessed once, so if
// the resource is accessed from the MemoryCache for a second time, there is
// no way to redo an integrity check.
//
// Thus, Blink implements a scheme where it caches the integrity information
// for those resources after the first time it is checked, and if there is
// another request for that resource, with the same integrity metadata, Blink
// skips the integrity calculation. However, if the integrity metadata is a
// mismatch, the MemoryCache must be skipped here, and a new request for the
// resource must be made to get the raw data. This is expected to be an
// uncommon case, however, as it implies two same-origin requests to the same
// resource, but with different integrity metadata.
if (existing_resource.MustRefetchDueToIntegrityMetadata(fetch_params)) {
return std::make_pair(kReload, "Reload due to resource integrity.");
}
// If the same URL has been loaded as a different type, we need to reload.
if (existing_resource.GetType() != type) {
// FIXME: If existingResource is a Preload and the new type is LinkPrefetch
// We really should discard the new prefetch since the preload has more
// specific type information! crbug.com/379893
// fast/dom/HTMLLinkElement/link-and-subresource-test hits this case.
return std::make_pair(kReload, "Reload due to type mismatch.");
}
// If resource was populated from archive or data: url, use it.
// This doesn't necessarily mean that |resource| was just created by using
// ResourceForStaticData().
if (is_static_data) {
return std::make_pair(kUse, "Use the existing static resource.");
}
if (existing_resource.CanReuse(fetch_params) != Resource::MatchStatus::kOk) {
return std::make_pair(kReload, "Reload due to Resource::CanReuse.");
}
// Don't reload resources while pasting.
if (allow_stale_resources_) {
return std::make_pair(
kUse, "Use the existing resource due to |allow_stale_resources_|.");
}
// FORCE_CACHE uses the cache no matter what.
if (request.GetCacheMode() == mojom::FetchCacheMode::kForceCache) {
return std::make_pair(
kUse, "Use the existing resource due to cache-mode: 'force-cache'.");
}
// Don't reuse resources with Cache-control: no-store.
if (existing_resource.HasCacheControlNoStoreHeader()) {
return std::make_pair(kReload, "Reload due to cache-control: no-sotre.");
}
// During the initial load, avoid loading the same resource multiple times for
// a single document, even if the cache policies would tell us to. We also
// group loads of the same resource together. Raw resources are exempted, as
// XHRs fall into this category and may have user-set Cache-Control: headers
// or other factors that require separate requests.
if (type != ResourceType::kRaw) {
if (!properties_->IsLoadComplete() &&
cached_resources_map_.Contains(
MemoryCache::RemoveFragmentIdentifierIfNeeded(
existing_resource.Url()))) {
return std::make_pair(kUse,
"Avoid making multiple requests for the same URL "
"during the initial load.");
}
if (existing_resource.IsLoading()) {
return std::make_pair(
kUse, "Use the existing resource because it's being loaded.");
}
}
// RELOAD always reloads
if (request.GetCacheMode() == mojom::FetchCacheMode::kBypassCache) {
return std::make_pair(kReload, "Reload due to cache-mode: 'reload'.");
}
// We'll try to reload the resource if it failed last time.
if (existing_resource.ErrorOccurred()) {
return std::make_pair(
kReload, "Reload because the existing resource has failed loading.");
}
// List of available images logic allows images to be re-used without cache
// validation. We restrict this only to images from memory cache which are the
// same as the version in the current document.
if (type == ResourceType::kImage &&
&existing_resource == CachedResource(request.Url())) {
return std::make_pair(kUse,
"Images can be reused without cache validation.");
}
if (existing_resource.MustReloadDueToVaryHeader(request)) {
return std::make_pair(kReload, "Reload due to vary header.");
}
// If any of the redirects in the chain to loading the resource were not
// cacheable, we cannot reuse our cached resource.
if (!existing_resource.CanReuseRedirectChain()) {
return std::make_pair(kReload, "Reload due to an uncacheable redirect.");
}
// Check if the cache headers requires us to revalidate (cache expiration for
// example).
if (request.GetCacheMode() == mojom::FetchCacheMode::kValidateCache ||
existing_resource.MustRevalidateDueToCacheHeaders(
request.AllowsStaleResponse()) ||
request.CacheControlContainsNoCache()) {
// Revalidation is harmful for non-matched preloads because it may lead to
// sharing one preloaded resource among multiple ResourceFetchers.
if (existing_resource.IsUnusedPreload()) {
return std::make_pair(
kReload, "Revalidation is harmful for non-matched preloads.");
}
// See if the resource has usable ETag or Last-modified headers. If the page
// is controlled by the ServiceWorker, we choose the Reload policy because
// the revalidation headers should not be exposed to the
// ServiceWorker.(crbug.com/429570)
//
// TODO(falken): If the controller has no fetch event handler, we probably
// can treat it as not being controlled in the S13nSW case. In the
// non-S13nSW, we don't know what controller the request will ultimately go
// to (due to skipWaiting) so be conservative.
if (existing_resource.CanUseCacheValidator() &&
properties_->GetControllerServiceWorkerMode() ==
mojom::ControllerServiceWorkerMode::kNoController) {
// If the resource is already a cache validator but not started yet, the
// |Use| policy should be applied to subsequent requests.
if (existing_resource.IsCacheValidator()) {
DCHECK(existing_resource.StillNeedsLoad());
return std::make_pair(
kUse,
"Merged to the revalidate request which has not yet started.");
}
return std::make_pair(kRevalidate, "");
}
// No, must reload.
return std::make_pair(kReload, "Reload due to missing cache validators.");
}
return std::make_pair(
kUse,
"Use the existing resource because there is no reason not to do so.");
}
void ResourceFetcher::SetAutoLoadImages(bool enable) {
if (enable == auto_load_images_)
return;
auto_load_images_ = enable;
if (!auto_load_images_)
return;
ReloadImagesIfNotDeferred();
}
void ResourceFetcher::SetImagesEnabled(bool enable) {
if (enable == images_enabled_)
return;
images_enabled_ = enable;
if (!images_enabled_)
return;
ReloadImagesIfNotDeferred();
}
bool ResourceFetcher::ShouldDeferImageLoad(const KURL& url) const {
return !Context().AllowImage(images_enabled_, url) || !auto_load_images_;
}
void ResourceFetcher::ReloadImagesIfNotDeferred() {
for (Resource* resource : document_resources_) {
if (resource->GetType() == ResourceType::kImage &&
resource->StillNeedsLoad() && !ShouldDeferImageLoad(resource->Url()))
StartLoad(resource);
}
}
FetchContext& ResourceFetcher::Context() const {
return *context_;
}
void ResourceFetcher::ClearContext() {
scheduler_->Shutdown();
ClearPreloads(ResourceFetcher::kClearAllPreloads);
{
// This block used to be
// context_ = Context().Detach();
// While we are splitting FetchContext to multiple classes we need to call
// "detach" for multiple objects in a coordinated manner. See
// https://crbug.com/914739 for the progress.
// TODO(yhirano): Remove the cross-class dependency.
context_ = Context().Detach();
properties_->Detach();
}
resource_load_observer_ = nullptr;
console_logger_->Detach();
loader_factory_ = nullptr;
// Make sure the only requests still going are keepalive requests.
// Callers of ClearContext() should be calling StopFetching() prior
// to this, but it's possible for additional requests to start during
// StopFetching() (e.g., fallback fonts that only trigger when the
// first choice font failed to load).
StopFetching();
if (!loaders_.IsEmpty() || !non_blocking_loaders_.IsEmpty()) {
// There are some keepalive requests.
// The use of WrapPersistent creates a reference cycle intentionally,
// to keep the ResourceFetcher and ResourceLoaders alive until the requests
// complete or the timer fires.
keepalive_loaders_task_handle_ = PostDelayedCancellableTask(
*task_runner_, FROM_HERE,
WTF::Bind(&ResourceFetcher::StopFetchingIncludingKeepaliveLoaders,
WrapPersistent(this)),
kKeepaliveLoadersTimeout);
}
}
int ResourceFetcher::BlockingRequestCount() const {
return loaders_.size();
}
int ResourceFetcher::NonblockingRequestCount() const {
return non_blocking_loaders_.size();
}
int ResourceFetcher::ActiveRequestCount() const {
return loaders_.size() + non_blocking_loaders_.size();
}
void ResourceFetcher::EnableIsPreloadedForTest() {
if (preloaded_urls_for_test_)
return;
preloaded_urls_for_test_ = std::make_unique<HashSet<String>>();
for (const auto& pair : preloads_) {
Resource* resource = pair.value;
preloaded_urls_for_test_->insert(resource->Url().GetString());
}
}
bool ResourceFetcher::IsPreloadedForTest(const KURL& url) const {
DCHECK(preloaded_urls_for_test_);
return preloaded_urls_for_test_->Contains(url.GetString());
}
void ResourceFetcher::ClearPreloads(ClearPreloadsPolicy policy) {
Vector<PreloadKey> keys_to_be_removed;
for (const auto& pair : preloads_) {
Resource* resource = pair.value;
if (policy == kClearAllPreloads || !resource->IsLinkPreload()) {
GetMemoryCache()->Remove(resource);
keys_to_be_removed.push_back(pair.key);
}
}
preloads_.RemoveAll(keys_to_be_removed);
matched_preloads_.clear();
}
Vector<KURL> ResourceFetcher::GetUrlsOfUnusedPreloads() {
Vector<KURL> urls;
for (const auto& pair : preloads_) {
Resource* resource = pair.value;
if (resource && resource->IsLinkPreload() && resource->IsUnusedPreload())
urls.push_back(resource->Url());
}
return urls;
}
void ResourceFetcher::HandleLoaderFinish(
Resource* resource,
TimeTicks response_end,
LoaderFinishType type,
uint32_t inflight_keepalive_bytes,
bool should_report_corb_blocking,
const std::vector<network::cors::PreflightTimingInfo>&
cors_preflight_timing_info) {
DCHECK(resource);
DCHECK_LE(inflight_keepalive_bytes, inflight_keepalive_bytes_);
inflight_keepalive_bytes_ -= inflight_keepalive_bytes;
ResourceLoader* loader = resource->Loader();
if (type == kDidFinishFirstPartInMultipart) {
// When loading a multipart resource, make the loader non-block when
// finishing loading the first part.
MoveResourceLoaderToNonBlocking(loader);
} else {
RemoveResourceLoader(loader);
DCHECK(!non_blocking_loaders_.Contains(loader));
}
DCHECK(!loaders_.Contains(loader));
const int64_t encoded_data_length =
resource->GetResponse().EncodedDataLength();
if (scoped_refptr<ResourceTimingInfo> info =
resource_timing_info_map_.Take(resource)) {
if (resource->GetResponse().IsHTTP() &&
resource->GetResponse().HttpStatusCode() < 400) {
info->SetInitialURL(resource->GetResourceRequest()
.GetInitialUrlForResourceTiming()
.IsNull()
? resource->GetResourceRequest().Url()
: resource->GetResourceRequest()
.GetInitialUrlForResourceTiming());
info->SetFinalResponse(resource->GetResponse());
info->SetLoadResponseEnd(response_end);
// encodedDataLength == -1 means "not available".
// TODO(ricea): Find cases where it is not available but the
// PerformanceResourceTiming spec requires it to be available and fix
// them.
info->AddFinalTransferSize(
encoded_data_length == -1 ? 0 : encoded_data_length);
if (resource->Options().request_initiator_context == kDocumentContext)
Context().AddResourceTiming(*info);
resource->ReportResourceTimingToClients(*info);
}
// Store additional timing info if CORS preflights are performed.
for (const auto& timing_info : cors_preflight_timing_info) {
// InitiatorType and InitialURL should be the same with each of the
// original request.
scoped_refptr<ResourceTimingInfo> preflight_info =
ResourceTimingInfo::Create(info->InitiatorType(),
timing_info.start_time);
preflight_info->SetInitialURL(info->InitialURL());
preflight_info->SetLoadResponseEnd(timing_info.response_end);
preflight_info->AddFinalTransferSize(timing_info.transfer_size);
// Set a provisional response to provide possible other information.
ResourceResponse response(info->InitialURL());
response.SetAlpnNegotiatedProtocol(
WebString::FromUTF8(timing_info.alpn_negotiated_protocol));
response.SetConnectionInfo(timing_info.connection_info);
response.SetHttpHeaderField(
http_names::kTimingAllowOrigin,
WebString::FromUTF8(timing_info.timing_allow_origin));
response.SetEncodedDataLength(timing_info.transfer_size);
preflight_info->SetFinalResponse(response);
Context().AddResourceTiming(*preflight_info);
}
}
resource->VirtualTimePauser().UnpauseVirtualTime();
if (type == kDidFinishLoading) {
resource->Finish(response_end, task_runner_.get());
// Since this resource came from the network stack we only schedule a stale
// while revalidate request if the network asked us to. If we called
// ShouldRevalidateStaleResponse here then the resource would be checking
// the freshness based on current time. It is possible that the resource
// is fresh at the time of the network stack handling but not at the time
// handling here and we should not be forcing a revalidation in that case.
// eg. network stack returning a resource with max-age=0.
if (resource->GetResourceRequest().AllowsStaleResponse() &&
resource->StaleRevalidationRequested()) {
ScheduleStaleRevalidate(resource);
}
}
if (resource_load_observer_) {
DCHECK(!IsDetached());
resource_load_observer_->DidFinishLoading(
resource->InspectorId(), response_end, encoded_data_length,
resource->GetResponse().DecodedBodyLength(),
should_report_corb_blocking,
ResourceLoadObserver::ResponseSource::kNotFromMemoryCache);
}
resource->ReloadIfLoFiOrPlaceholderImage(this, Resource::kReloadIfNeeded);
}
void ResourceFetcher::HandleLoaderError(Resource* resource,
const ResourceError& error,
uint32_t inflight_keepalive_bytes) {
DCHECK(resource);
DCHECK_LE(inflight_keepalive_bytes, inflight_keepalive_bytes_);
inflight_keepalive_bytes_ -= inflight_keepalive_bytes;
RemoveResourceLoader(resource->Loader());
resource_timing_info_map_.Take(resource);
resource->VirtualTimePauser().UnpauseVirtualTime();
if (error.IsCancellation())
RemovePreload(resource);
if (network_utils::IsCertificateTransparencyRequiredError(
error.ErrorCode())) {
Context().CountUsage(
mojom::WebFeature::kCertificateTransparencyRequiredErrorOnResourceLoad);
}
resource->FinishAsError(error, task_runner_.get());
if (resource_load_observer_) {
DCHECK(!IsDetached());
const bool is_internal_request = resource->Options().initiator_info.name ==
fetch_initiator_type_names::kInternal;
resource_load_observer_->DidFailLoading(
resource->LastResourceRequest().Url(), resource->InspectorId(), error,
resource->GetResponse().EncodedDataLength(), is_internal_request);
}
resource->ReloadIfLoFiOrPlaceholderImage(this, Resource::kReloadIfNeeded);
}
void ResourceFetcher::MoveResourceLoaderToNonBlocking(ResourceLoader* loader) {
DCHECK(loader);
DCHECK(loaders_.Contains(loader));
non_blocking_loaders_.insert(loader);
loaders_.erase(loader);
}
bool ResourceFetcher::StartLoad(Resource* resource) {
DCHECK(resource);
DCHECK(resource->StillNeedsLoad());
ResourceRequest request(resource->GetResourceRequest());
ResourceLoader* loader = nullptr;
{
// Forbids JavaScript/revalidation until start()
// to prevent unintended state transitions.
Resource::RevalidationStartForbiddenScope
revalidation_start_forbidden_scope(resource);
ScriptForbiddenScope script_forbidden_scope;
if (properties_->ShouldBlockLoadingSubResource() && IsMainThread()) {
GetMemoryCache()->Remove(resource);
return false;
}
ResourceResponse response;
blink::probe::PlatformSendRequest probe(&Context(), resource->InspectorId(),
request, response,
resource->Options().initiator_info);
resource->VirtualTimePauser().PauseVirtualTime();
if (resource_load_observer_) {
DCHECK(!IsDetached());
resource_load_observer_->WillSendRequest(
resource->InspectorId(), request, response, resource->GetType(),
resource->Options().initiator_info);
}
// TODO(shaochuan): Saving modified ResourceRequest back to |resource|,
// remove once dispatchWillSendRequest() takes const ResourceRequest.
// crbug.com/632580
resource->SetResourceRequest(request);
using QuotaType = decltype(inflight_keepalive_bytes_);
QuotaType size = 0;
if (request.GetKeepalive() && request.HttpBody()) {
auto original_size = request.HttpBody()->SizeInBytes();
DCHECK_LE(inflight_keepalive_bytes_, kKeepaliveInflightBytesQuota);
if (original_size > std::numeric_limits<QuotaType>::max())
return false;
size = static_cast<QuotaType>(original_size);
if (kKeepaliveInflightBytesQuota - inflight_keepalive_bytes_ < size)
return false;
inflight_keepalive_bytes_ += size;
}
loader =
MakeGarbageCollected<ResourceLoader>(this, scheduler_, resource, size);
if (resource->ShouldBlockLoadEvent())
loaders_.insert(loader);
else
non_blocking_loaders_.insert(loader);
StorePerformanceTimingInitiatorInformation(resource);
}
loader->Start();
{
Resource::RevalidationStartForbiddenScope
revalidation_start_forbidden_scope(resource);
ScriptForbiddenScope script_forbidden_scope;
// NotifyStartLoad() shouldn't cause AddClient/RemoveClient().
Resource::ProhibitAddRemoveClientInScope
prohibit_add_remove_client_in_scope(resource);
if (!resource->IsLoaded())
resource->NotifyStartLoad();
}
return true;
}
void ResourceFetcher::RemoveResourceLoader(ResourceLoader* loader) {
DCHECK(loader);
if (loaders_.Contains(loader))
loaders_.erase(loader);
else if (non_blocking_loaders_.Contains(loader))
non_blocking_loaders_.erase(loader);
else
NOTREACHED();
if (loaders_.IsEmpty() && non_blocking_loaders_.IsEmpty())
keepalive_loaders_task_handle_.Cancel();
}
void ResourceFetcher::StopFetching() {
StopFetchingInternal(StopFetchingTarget::kExcludingKeepaliveLoaders);
}
void ResourceFetcher::SetDefersLoading(bool defers) {
for (const auto& loader : non_blocking_loaders_)
loader->SetDefersLoading(defers);
for (const auto& loader : loaders_)
loader->SetDefersLoading(defers);
}
void ResourceFetcher::UpdateAllImageResourcePriorities() {
TRACE_EVENT0(
"blink",
"ResourceLoadPriorityOptimizer::updateAllImageResourcePriorities");
for (Resource* resource : document_resources_) {
if (!resource || resource->GetType() != ResourceType::kImage ||
!resource->IsLoading())
continue;
ResourcePriority resource_priority = resource->PriorityFromObservers();
ResourceLoadPriority resource_load_priority = ComputeLoadPriority(
ResourceType::kImage, resource->GetResourceRequest(),
resource_priority.visibility);
if (resource_load_priority == resource->GetResourceRequest().Priority())
continue;
resource->DidChangePriority(resource_load_priority,
resource_priority.intra_priority_value);
TRACE_EVENT_NESTABLE_ASYNC_INSTANT1(
TRACE_DISABLED_BY_DEFAULT("network"), "ResourcePrioritySet",
TRACE_ID_WITH_SCOPE("BlinkResourceID",
TRACE_ID_LOCAL(resource->InspectorId())),
"data", ResourcePrioritySetData(resource_load_priority));
DCHECK(!IsDetached());
resource_load_observer_->DidChangePriority(
resource->InspectorId(), resource_load_priority,
resource_priority.intra_priority_value);
}
}
void ResourceFetcher::ReloadLoFiImages() {
for (Resource* resource : document_resources_) {
if (resource)
resource->ReloadIfLoFiOrPlaceholderImage(this, Resource::kReloadAlways);
}
}
String ResourceFetcher::GetCacheIdentifier() const {
if (properties_->GetControllerServiceWorkerMode() !=
mojom::ControllerServiceWorkerMode::kNoController)
return String::Number(properties_->ServiceWorkerId());
return MemoryCache::DefaultCacheIdentifier();
}
void ResourceFetcher::OnNetworkQuiet() {
scheduler_->OnNetworkQuiet();
}
void ResourceFetcher::EmulateLoadStartedForInspector(
Resource* resource,
const KURL& url,
mojom::RequestContextType request_context,
const AtomicString& initiator_name) {
if (CachedResource(url))
return;
ResourceRequest resource_request(url);
resource_request.SetRequestContext(request_context);
ResourceLoaderOptions options = resource->Options();
options.initiator_info.name = initiator_name;
FetchParameters params(resource_request, options);
Context().CanRequest(resource->GetType(), resource->LastResourceRequest(),
resource->LastResourceRequest().Url(), params.Options(),
SecurityViolationReportingPolicy::kReport,
resource->LastResourceRequest().GetRedirectStatus());
RequestLoadStarted(resource->InspectorId(), resource, params, kUse);
}
void ResourceFetcher::PrepareForLeakDetection() {
// Stop loaders including keepalive ones that may persist after page
// navigation and thus affect instance counters of leak detection.
StopFetchingIncludingKeepaliveLoaders();
}
void ResourceFetcher::SetStaleWhileRevalidateEnabled(bool enabled) {
stale_while_revalidate_enabled_ = enabled;
}
void ResourceFetcher::StopFetchingInternal(StopFetchingTarget target) {
// TODO(toyoshim): May want to suspend scheduler while canceling loaders so
// that the cancellations below do not awake unnecessary scheduling.
HeapVector<Member<ResourceLoader>> loaders_to_cancel;
for (const auto& loader : non_blocking_loaders_) {
if (target == StopFetchingTarget::kIncludingKeepaliveLoaders ||
!loader->ShouldBeKeptAliveWhenDetached()) {
loaders_to_cancel.push_back(loader);
}
}
for (const auto& loader : loaders_) {
if (target == StopFetchingTarget::kIncludingKeepaliveLoaders ||
!loader->ShouldBeKeptAliveWhenDetached()) {
loaders_to_cancel.push_back(loader);
}
}
for (const auto& loader : loaders_to_cancel) {
if (loaders_.Contains(loader) || non_blocking_loaders_.Contains(loader))
loader->Cancel();
}
}
void ResourceFetcher::StopFetchingIncludingKeepaliveLoaders() {
StopFetchingInternal(StopFetchingTarget::kIncludingKeepaliveLoaders);
}
void ResourceFetcher::ScheduleStaleRevalidate(Resource* stale_resource) {
if (stale_resource->StaleRevalidationStarted())
return;
stale_resource->SetStaleRevalidationStarted();
task_runner_->PostTask(
FROM_HERE,
WTF::Bind(&ResourceFetcher::RevalidateStaleResource,
WrapWeakPersistent(this), WrapPersistent(stale_resource)));
}
void ResourceFetcher::RevalidateStaleResource(Resource* stale_resource) {
// Creating FetchParams from Resource::GetResourceRequest doesn't create
// the exact same request as the original one, while for revalidation
// purpose this is probably fine.
// TODO(dtapuska): revisit this when we have a better way to re-dispatch
// requests.
FetchParameters params(stale_resource->GetResourceRequest());
params.SetStaleRevalidation(true);
params.MutableResourceRequest().SetSkipServiceWorker(true);
RawResource::Fetch(
params, this,
MakeGarbageCollected<StaleRevalidationResourceClient>(stale_resource));
}
mojom::blink::BlobRegistry* ResourceFetcher::GetBlobRegistry() {
if (!blob_registry_ptr_) {
Platform::Current()->GetInterfaceProvider()->GetInterface(
MakeRequest(&blob_registry_ptr_, task_runner_));
}
return blob_registry_ptr_.get();
}
FrameScheduler* ResourceFetcher::GetFrameScheduler() {
return frame_scheduler_.get();
}
void ResourceFetcher::Trace(blink::Visitor* visitor) {
visitor->Trace(context_);
visitor->Trace(properties_);
visitor->Trace(resource_load_observer_);
visitor->Trace(console_logger_);
visitor->Trace(loader_factory_);
visitor->Trace(scheduler_);
visitor->Trace(archive_);
visitor->Trace(loaders_);
visitor->Trace(non_blocking_loaders_);
visitor->Trace(cached_resources_map_);
visitor->Trace(document_resources_);
visitor->Trace(preloads_);
visitor->Trace(matched_preloads_);
visitor->Trace(resource_timing_info_map_);
}
// static
const ResourceFetcher::ResourceFetcherSet&
ResourceFetcher::MainThreadFetchers() {
return MainThreadFetchersSet();
}
} // namespace blink