blob: 63cb679368a82e9807055708897d6db7676f13c8 [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/feature_list.h"
#include "base/metrics/field_trial_params.h"
#include "base/time/time.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/public/platform/platform.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/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_context.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.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_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_instrumentation.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/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, resource_histogram, \
("Blink.MemoryCache.RevalidationPolicy." prefix #name, kLoad + 1)); \
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, MainResource) \
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::kMainResource:
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();
DCHECK(importance_mode == mojom::FetchImportanceMode::kImportanceAuto ||
RuntimeEnabledFeatures::PriorityHintsEnabled());
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;
}
const base::Feature kStaleWhileRevalidateExperiment{
"StaleWhileRevalidateExperiment", base::FEATURE_DISABLED_BY_DEFAULT};
bool MatchesStaleWhileRevalidateControlList(const String& host) {
DEFINE_STATIC_LOCAL(String, kStaleWhileRevalidateControlHosts,
(GetFieldTrialParamValueByFeature(
kStaleWhileRevalidateExperiment, "control_hosts")
.c_str()));
return !host.IsEmpty() &&
kStaleWhileRevalidateControlHosts.Find(host) != kNotFound;
}
bool MatchesStaleWhileRevalidateAllowList(const String& host) {
DEFINE_STATIC_LOCAL(String, kStaleWhileRevalidateAllowHosts,
(GetFieldTrialParamValueByFeature(
kStaleWhileRevalidateExperiment, "hosts")
.c_str()));
return !host.IsEmpty() &&
kStaleWhileRevalidateAllowHosts.Find(host) != kNotFound;
}
} // namespace
ResourceFetcherInit::ResourceFetcherInit(
const ResourceFetcherProperties& properties,
FetchContext* context)
: ResourceFetcherInit(properties,
context,
*MakeGarbageCollected<NullConsoleLogger>()) {}
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) {
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);
// 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(Context().ModifyPriorityForExperiments(priority),
resource_request.Priority());
}
mojom::RequestContextType ResourceFetcher::DetermineRequestContext(
ResourceType type,
IsImageSet is_image_set,
bool is_main_frame) {
DCHECK((is_image_set == kImageNotImageSet) ||
(type == ResourceType::kImage && is_image_set == kImageIsImageSet));
switch (type) {
case ResourceType::kMainResource:
if (!is_main_frame)
return mojom::RequestContextType::IFRAME;
// FIXME: Change this to a context frame type (once we introduce them):
// http://fetch.spec.whatwg.org/#concept-request-context-frame-type
return mojom::RequestContextType::HYPERLINK;
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;
}
// A delegating ResourceFetcherProperties subclass which can be from the
// original ResourceFetcherProperties.
class ResourceFetcher::DetachableProperties final
: public ResourceFetcherProperties {
public:
explicit DetachableProperties(const ResourceFetcherProperties& properties)
: properties_(properties) {}
~DetachableProperties() override = default;
void Detach() {
if (!properties_) {
// Already detached.
return;
}
is_main_frame_ = properties_->IsMainFrame();
properties_ = nullptr;
}
void Trace(Visitor* visitor) override {
visitor->Trace(properties_);
ResourceFetcherProperties::Trace(visitor);
}
bool IsMainFrame() const override {
return properties_ ? properties_->IsMainFrame() : is_main_frame_;
}
private:
// |properties_| is null if and only if detached.
Member<const ResourceFetcherProperties> properties_;
// The following members are used when detached.
bool is_main_frame_ = false;
};
ResourceFetcher::ResourceFetcher(const ResourceFetcherInit& init)
: properties_(
*MakeGarbageCollected<DetachableProperties>(*init.properties)),
context_(init.context),
console_logger_(init.console_logger),
archive_(init.archive),
resource_timing_report_timer_(
Context().GetLoadingTaskRunner(),
this,
&ResourceFetcher::ResourceTimingReportTimerFired),
auto_load_images_(true),
images_enabled_(true),
allow_stale_resources_(false),
image_fetched_(false),
stale_while_revalidate_enabled_(false) {
DCHECK(console_logger_);
InstanceCounters::IncrementCounter(InstanceCounters::kResourceFetcherCounter);
if (IsMainThread())
MainThreadFetchersSet().insert(this);
context_->Bind(this);
scheduler_ = MakeGarbageCollected<ResourceLoadScheduler>(
init.initial_throttling_policy, context_);
}
ResourceFetcher::~ResourceFetcher() {
InstanceCounters::DecrementCounter(InstanceCounters::kResourceFetcherCounter);
}
const ResourceFetcherProperties& ResourceFetcher::GetProperties() const {
return *properties_;
}
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();
}
blink::mojom::ControllerServiceWorkerMode
ResourceFetcher::IsControlledByServiceWorker() const {
return Context().IsControlledByServiceWorker();
}
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(unsigned long 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(),
resource->GetType() == ResourceType::kMainResource);
info->SetInitialURL(resource->GetResourceRequest().Url());
ResourceResponse final_response = resource->GetResponse();
final_response.SetResourceLoadTiming(nullptr);
info->SetFinalResponse(final_response);
info->SetLoadFinishTime(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(
unsigned long identifier,
Resource* resource,
const ResourceRequest& original_request) {
ResourceRequest request = original_request;
Context().DispatchWillSendRequest(
identifier, request, ResourceResponse() /* redirects */,
resource->GetType(), resource->Options().initiator_info);
Context().DispatchDidReceiveResponse(
identifier, request, resource->GetResponse(), resource,
FetchContext::ResourceResponseType::kFromMemoryCache);
if (resource->EncodedSize() > 0) {
Context().DispatchDidReceiveData(identifier, nullptr,
resource->EncodedSize());
}
Context().DispatchDidFinishLoading(
identifier, TimeTicks(), 0, resource->GetResponse().DecodedBodyLength(),
false);
}
static std::unique_ptr<TracedValue> UrlForTraceEvent(const KURL& url) {
std::unique_ptr<TracedValue> value = TracedValue::Create();
value->SetString("url", url.GetString());
return value;
}
Resource* ResourceFetcher::ResourceForStaticData(
const FetchParameters& params,
const ResourceFactory& factory,
const SubstituteData& substitute_data) {
const KURL& url = params.GetResourceRequest().Url();
DCHECK(url.ProtocolIsData() || substitute_data.IsValid() || archive_);
if (!archive_ && !substitute_data.IsValid() &&
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 (substitute_data.IsValid()) {
data = substitute_data.Content();
response.SetCurrentRequestUrl(url);
response.SetMimeType(substitute_data.MimeType());
response.SetExpectedContentLength(data->size());
response.SetTextEncodingName(substitute_data.TextEncoding());
} else if (url.ProtocolIsData()) {
data = network_utils::ParseDataURLAndPopulateResponse(url, response);
if (!data)
return nullptr;
// |response| is modified by parseDataURLAndPopulateResponse() and is
// ready to be used.
} 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, nullptr);
resource->SetDataBufferingPolicy(kBufferData);
if (data->size())
resource->SetResourceBuffer(data);
resource->SetIdentifier(CreateUniqueIdentifier());
resource->SetCacheIdentifier(cache_identifier);
resource->Finish(TimeTicks(), Context().GetLoadingTaskRunner().get());
if (!substitute_data.IsValid())
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, Context().GetLoadingTaskRunner().get());
resource->FinishAsError(ResourceError::CancelledDueToAccessCheckError(
params.Url(), blocked_reason),
Context().GetLoadingTaskRunner().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,
const SubstituteData& substitute_data,
unsigned long identifier) {
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()));
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, Context().IsMainFrame()));
}
if (resource_type == ResourceType::kLinkPrefetch)
resource_request.SetHTTPHeaderField(http_names::kPurpose, "prefetch");
bool resource_allows_stale_while_revalidate = false;
bool host_matches_control =
MatchesStaleWhileRevalidateControlList(params.Url().Host());
bool host_matches_allow = false;
if (!host_matches_control) {
host_matches_allow =
MatchesStaleWhileRevalidateAllowList(params.Url().Host());
}
if (host_matches_allow)
resource_allows_stale_while_revalidate = host_matches_allow;
// For the finch experiment indicate that the resource likely supports
// stale while revalidate (based on a list of hosts). This allows us
// to only log metrics for sites that would possibly benefit from
// stale while revalidate being enabled.
resource_request.SetStaleRevalidateCandidate(
(host_matches_allow || host_matches_control) &&
!params.IsStaleRevalidation());
// 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), non-GET requests and resource requests that
// are raw. We are explicitly excluding RawResources here to avoid
// unintentional SWR, as bugs around RawResources tend to be complicated and
// critical.
resource_request.SetAllowStaleResponse(
(resource_allows_stale_while_revalidate ||
stale_while_revalidate_enabled_) &&
resource_request.HttpMethod() == http_names::kGET &&
!IsRawResource(resource_type) && !params.IsStaleRevalidation());
Context().AddAdditionalRequestHeaders(
resource_request, (resource_type == ResourceType::kMainResource)
? kFetchMainResource
: kFetchSubresource);
network_instrumentation::ResourcePrioritySet(identifier,
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().IsAdResource(url, resource_type,
resource_request.GetRequestContext())) {
resource_request.SetIsAdResource();
}
if (blocked_reason)
return blocked_reason;
// For initial requests, call prepareRequest() here before revalidation
// policy is determined.
Context().PrepareRequest(resource_request,
FetchContext::RedirectType::kNotForRedirect);
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,
const SubstituteData& substitute_data,
unsigned long identifier) {
if (!identifier)
identifier = CreateUniqueIdentifier();
ResourceRequest& resource_request = params.MutableResourceRequest();
network_instrumentation::ScopedResourceLoadTracker
scoped_resource_load_tracker(identifier, resource_request);
SCOPED_BLINK_UMA_HISTOGRAM_TIMER_THREAD_SAFE(
"Blink.Fetch.RequestResourceTime");
TRACE_EVENT1("blink", "ResourceFetcher::requestResource", "url",
UrlForTraceEvent(params.Url()));
// 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);
}
}
// |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(Context().GetSecurityOrigin());
base::Optional<ResourceRequestBlockedReason> blocked_reason =
PrepareRequest(params, factory, substitute_data, identifier);
if (blocked_reason) {
return ResourceForBlockedRequest(params, factory, blocked_reason.value(),
client);
}
ResourceType resource_type = factory.GetType();
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 || substitute_data.IsValid() || archive_;
bool is_stale_revalidation = params.IsStaleRevalidation();
if (!is_stale_revalidation && is_static_data) {
resource = ResourceForStaticData(params, factory, substitute_data);
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:
bool used_stale = false;
if (resource_request.AllowsStaleResponse() &&
resource->ShouldRevalidateStaleResponse()) {
used_stale = true;
ScheduleStaleRevalidate(resource);
}
if (resource_request.IsStaleRevalidateCandidate()) {
context_->DidObserveLoadingBehavior(
used_stale
? WebLoadingBehaviorFlag::
kStaleWhileRevalidateResourceCandidateStaleCacheLoad
: WebLoadingBehaviorFlag::
kStaleWhileRevalidateResourceCandidateCacheLoad);
}
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->SetIdentifier(identifier);
if (client)
client->SetResource(resource, Context().GetLoadingTaskRunner().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()),
Context().GetLoadingTaskRunner().get());
}
}
if (policy != kUse)
InsertAsPreloadIfNecessary(resource, params, resource_type);
if (resource->Identifier() == identifier &&
(resource->StillNeedsLoad() || resource->IsLoading())) {
scoped_resource_load_tracker.ResourceLoadContinuesBeyondScope();
}
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(Context().IsControlledByServiceWorker() ==
blink::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);
}
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;
if (resource->GetType() == ResourceType::kMainResource)
return;
scoped_refptr<ResourceTimingInfo> info = ResourceTimingInfo::Create(
fetch_initiator, CurrentTimeTicks(), false /* is_main_resource */);
if (resource->IsCacheValidator()) {
const AtomicString& timing_allow_origin =
resource->GetResponse().HttpHeaderField(http_names::kTimingAllowOrigin);
if (!timing_allow_origin.IsEmpty())
info->SetOriginalTimingAllowOrigin(timing_allow_origin);
}
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, Context().GetLoadingTaskRunner().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_->AddWarningMessage(ConsoleLogger::Source::kOther,
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 = DetermineRevalidationPolicyInternal(
type, fetch_params, existing_resource, is_static_data);
TRACE_EVENT_INSTANT1("blink", "ResourceFetcher::DetermineRevalidationPolicy",
TRACE_EVENT_SCOPE_THREAD, "revalidationPolicy", policy);
return policy;
}
ResourceFetcher::RevalidationPolicy
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 kReload;
if (IsImageResourceDisallowedToBeReused(existing_resource))
return kReload;
// 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 kReload;
}
// 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 kReload;
}
// 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 kReload;
}
// 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.
RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy "
"reloading due to type mismatch.";
return kReload;
}
// If resource was populated from a SubstituteData load or data: url, use it.
// This doesn't necessarily mean that |resource| was just created by using
// ResourceForStaticData().
if (is_static_data)
return kUse;
if (existing_resource.CanReuse(fetch_params) != Resource::MatchStatus::kOk) {
RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy "
"reloading due to Resource::CanReuse() "
"returning false.";
return kReload;
}
// Don't reload resources while pasting.
if (allow_stale_resources_)
return kUse;
// FORCE_CACHE uses the cache no matter what.
if (request.GetCacheMode() == mojom::FetchCacheMode::kForceCache)
return kUse;
// Don't reuse resources with Cache-control: no-store.
if (existing_resource.HasCacheControlNoStoreHeader()) {
RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy "
"reloading due to Cache-control: no-store.";
return kReload;
}
// 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 (!Context().IsLoadComplete() &&
cached_resources_map_.Contains(
MemoryCache::RemoveFragmentIdentifierIfNeeded(
existing_resource.Url())))
return kUse;
if (existing_resource.IsLoading())
return kUse;
}
// RELOAD always reloads
if (request.GetCacheMode() == mojom::FetchCacheMode::kBypassCache) {
RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy "
"reloading due to "
"FetchCacheMode::kBypassCache";
return kReload;
}
// We'll try to reload the resource if it failed last time.
if (existing_resource.ErrorOccurred()) {
RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy "
"reloading due to resource being in the error "
"state";
return kReload;
}
// 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 kUse;
}
if (existing_resource.MustReloadDueToVaryHeader(request))
return kReload;
// 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()) {
RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy "
"reloading due to an uncacheable redirect";
return kReload;
}
// 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 kReload;
// 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() &&
Context().IsControlledByServiceWorker() ==
blink::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 kUse;
}
return kRevalidate;
}
// No, must reload.
RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy "
"reloading due to missing cache validators.";
return kReload;
}
return kUse;
}
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.
auto* context = context_.Get();
context_ = Context().Detach();
context->Unbind();
properties_->Detach();
context_->Bind(this);
}
console_logger_ = MakeGarbageCollected<NullConsoleLogger>();
// 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(
*Context().GetLoadingTaskRunner(), 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;
}
ArchiveResource* ResourceFetcher::CreateArchive(
const KURL& url,
scoped_refptr<const SharedBuffer> buffer) {
// Only the top-frame can load MHTML.
if (!Context().IsMainFrame()) {
console_logger_->AddErrorMessage(
ConsoleLogger::Source::kScript,
"Attempted to load a multipart archive into an subframe: " +
url.GetString());
return nullptr;
}
archive_ = MHTMLArchive::Create(url, buffer);
if (!archive_) {
// Log if attempting to load an invalid archive resource.
console_logger_->AddErrorMessage(
ConsoleLogger::Source::kScript,
"Malformed multipart archive: " + url.GetString());
return nullptr;
}
return archive_->MainResource();
}
void ResourceFetcher::HandleLoadCompletion(Resource* resource) {
Context().DidLoadResource(resource);
resource->ReloadIfLoFiOrPlaceholderImage(this, Resource::kReloadIfNeeded);
}
void ResourceFetcher::HandleLoaderFinish(
Resource* resource,
TimeTicks finish_time,
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().Url());
info->SetFinalResponse(resource->GetResponse());
info->SetLoadFinishTime(finish_time);
// 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, false);
preflight_info->SetInitialURL(info->InitialURL());
preflight_info->SetLoadFinishTime(timing_info.finish_time);
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();
Context().DispatchDidFinishLoading(
resource->Identifier(), finish_time, encoded_data_length,
resource->GetResponse().DecodedBodyLength(), should_report_corb_blocking);
if (type == kDidFinishLoading) {
resource->Finish(finish_time, Context().GetLoadingTaskRunner().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.
bool used_stale = false;
if (resource->GetResourceRequest().AllowsStaleResponse() &&
resource->StaleRevalidationRequested()) {
used_stale = true;
ScheduleStaleRevalidate(resource);
}
if (resource->GetResourceRequest().IsStaleRevalidateCandidate()) {
WebLoadingBehaviorFlag behavior = WebLoadingBehaviorFlag::
kStaleWhileRevalidateResourceCandidateCacheLoad;
if (used_stale) {
behavior = WebLoadingBehaviorFlag::
kStaleWhileRevalidateResourceCandidateStaleCacheLoad;
} else if (resource->NetworkAccessed()) {
behavior = WebLoadingBehaviorFlag::
kStaleWhileRevalidateResourceCandidateNetworkLoad;
}
context_->DidObserveLoadingBehavior(behavior);
}
}
HandleLoadCompletion(resource);
}
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);
bool is_internal_request = resource->Options().initiator_info.name ==
fetch_initiator_type_names::kInternal;
resource->VirtualTimePauser().UnpauseVirtualTime();
Context().DispatchDidFail(
resource->LastResourceRequest().Url(), resource->Identifier(), error,
resource->GetResponse().EncodedDataLength(), is_internal_request);
if (error.IsCancellation())
RemovePreload(resource);
resource->FinishAsError(error, Context().GetLoadingTaskRunner().get());
HandleLoadCompletion(resource);
}
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 (!Context().ShouldLoadNewResource(resource->GetType()) &&
IsMainThread()) {
GetMemoryCache()->Remove(resource);
return false;
}
ResourceResponse response;
blink::probe::PlatformSendRequest probe(&Context(), resource->Identifier(),
request, response,
resource->Options().initiator_info);
if (Context().GetFrameScheduler()) {
WebScopedVirtualTimePauser virtual_time_pauser =
Context().GetFrameScheduler()->CreateWebScopedVirtualTimePauser(
resource->Url().GetString(),
WebScopedVirtualTimePauser::VirtualTaskDuration::kNonInstant);
virtual_time_pauser.PauseVirtualTime();
resource->VirtualTimePauser() = std::move(virtual_time_pauser);
}
Context().DispatchWillSendRequest(resource->Identifier(), 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 = ResourceLoader::Create(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);
network_instrumentation::ResourcePrioritySet(resource->Identifier(),
resource_load_priority);
Context().DispatchDidChangeResourcePriority(
resource->Identifier(), 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 (Context().IsControlledByServiceWorker() !=
blink::mojom::ControllerServiceWorkerMode::kNoController)
return String::Number(Context().ServiceWorkerID());
return MemoryCache::DefaultCacheIdentifier();
}
void ResourceFetcher::OnNetworkQuiet() {
Context().DispatchNetworkQuiet();
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->Identifier(), 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();
Context().GetLoadingTaskRunner()->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);
RawResource::Fetch(
params, this,
MakeGarbageCollected<StaleRevalidationResourceClient>(stale_resource));
}
void ResourceFetcher::Trace(blink::Visitor* visitor) {
visitor->Trace(context_);
visitor->Trace(properties_);
visitor->Trace(console_logger_);
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