| /* |
| 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 "platform/loader/fetch/ResourceFetcher.h" |
| |
| #include "platform/Histogram.h" |
| #include "platform/RuntimeEnabledFeatures.h" |
| #include "platform/ScriptForbiddenScope.h" |
| #include "platform/instrumentation/tracing/TraceEvent.h" |
| #include "platform/instrumentation/tracing/TracedValue.h" |
| #include "platform/loader/fetch/FetchContext.h" |
| #include "platform/loader/fetch/FetchInitiatorTypeNames.h" |
| #include "platform/loader/fetch/MemoryCache.h" |
| #include "platform/loader/fetch/RawResource.h" |
| #include "platform/loader/fetch/ResourceLoader.h" |
| #include "platform/loader/fetch/ResourceLoadingLog.h" |
| #include "platform/loader/fetch/ResourceTimingInfo.h" |
| #include "platform/loader/fetch/UniqueIdentifier.h" |
| #include "platform/mhtml/ArchiveResource.h" |
| #include "platform/mhtml/MHTMLArchive.h" |
| #include "platform/network/NetworkInstrumentation.h" |
| #include "platform/network/NetworkUtils.h" |
| #include "platform/probe/PlatformProbes.h" |
| #include "platform/weborigin/KnownPorts.h" |
| #include "platform/weborigin/SecurityOrigin.h" |
| #include "platform/weborigin/SecurityPolicy.h" |
| #include "platform/weborigin/SecurityViolationReportingPolicy.h" |
| #include "platform/wtf/Assertions.h" |
| #include "platform/wtf/text/CString.h" |
| #include "platform/wtf/text/WTFString.h" |
| #include "public/platform/Platform.h" |
| #include "public/platform/WebCachePolicy.h" |
| #include "public/platform/WebURL.h" |
| #include "public/platform/WebURLRequest.h" |
| |
| using blink::WebURLRequest; |
| |
| namespace blink { |
| |
| namespace { |
| |
| #define DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, name) \ |
| case Resource::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, Media) \ |
| 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) \ |
| } |
| |
| void AddRedirectsToTimingInfo(Resource* resource, ResourceTimingInfo* info) { |
| // Store redirect responses that were packed inside the final response. |
| const auto& responses = resource->GetResponse().RedirectResponses(); |
| for (size_t i = 0; i < responses.size(); ++i) { |
| const KURL& new_url = i + 1 < responses.size() |
| ? KURL(responses[i + 1].Url()) |
| : resource->GetResourceRequest().Url(); |
| bool cross_origin = |
| !SecurityOrigin::AreSameSchemeHostPort(responses[i].Url(), new_url); |
| info->AddRedirect(responses[i], cross_origin); |
| } |
| } |
| |
| ResourceLoadPriority TypeToPriority(Resource::Type type) { |
| switch (type) { |
| case Resource::kMainResource: |
| case Resource::kCSSStyleSheet: |
| case Resource::kFont: |
| // Also parser-blocking scripts (set explicitly in loadPriority) |
| return kResourceLoadPriorityVeryHigh; |
| case Resource::kXSLStyleSheet: |
| DCHECK(RuntimeEnabledFeatures::XSLTEnabled()); |
| case Resource::kRaw: |
| case Resource::kImportResource: |
| case Resource::kScript: |
| // Also visible resources/images (set explicitly in loadPriority) |
| return kResourceLoadPriorityHigh; |
| case Resource::kManifest: |
| case Resource::kMock: |
| // Also late-body scripts discovered by the preload scanner (set |
| // explicitly in loadPriority) |
| return kResourceLoadPriorityMedium; |
| case Resource::kImage: |
| case Resource::kTextTrack: |
| case Resource::kMedia: |
| case Resource::kSVGDocument: |
| // Also async scripts (set explicitly in loadPriority) |
| return kResourceLoadPriorityLow; |
| case Resource::kLinkPrefetch: |
| return kResourceLoadPriorityVeryLow; |
| } |
| |
| NOTREACHED(); |
| return kResourceLoadPriorityUnresolved; |
| } |
| |
| 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; |
| return true; |
| } |
| |
| } // namespace |
| |
| ResourceLoadPriority ResourceFetcher::ComputeLoadPriority( |
| Resource::Type 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 = kResourceLoadPriorityHigh; |
| |
| // 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 == Resource::kImage && !is_link_preload) |
| image_fetched_ = true; |
| |
| // A preloaded font should not take precedence over critical CSS or |
| // parser-blocking scripts. |
| if (type == Resource::kFont && is_link_preload) |
| priority = kResourceLoadPriorityHigh; |
| |
| if (FetchParameters::kIdleLoad == defer_option) { |
| priority = kResourceLoadPriorityVeryLow; |
| } else if (type == Resource::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 = kResourceLoadPriorityLow; |
| } 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 = kResourceLoadPriorityMedium; |
| } |
| } else if (FetchParameters::kLazyLoad == defer_option) { |
| priority = kResourceLoadPriorityVeryLow; |
| } else if (resource_request.GetRequestContext() == |
| WebURLRequest::kRequestContextBeacon || |
| resource_request.GetRequestContext() == |
| WebURLRequest::kRequestContextPing || |
| resource_request.GetRequestContext() == |
| WebURLRequest::kRequestContextCSPReport) { |
| priority = kResourceLoadPriorityVeryLow; |
| } |
| |
| // 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(priority, resource_request.Priority()); |
| } |
| |
| static void PopulateTimingInfo(ResourceTimingInfo* info, Resource* resource) { |
| KURL initial_url = resource->GetResponse().RedirectResponses().IsEmpty() |
| ? resource->GetResourceRequest().Url() |
| : resource->GetResponse().RedirectResponses()[0].Url(); |
| info->SetInitialURL(initial_url); |
| info->SetFinalResponse(resource->GetResponse()); |
| } |
| |
| WebURLRequest::RequestContext ResourceFetcher::DetermineRequestContext( |
| Resource::Type type, |
| IsImageSet is_image_set, |
| bool is_main_frame) { |
| DCHECK((is_image_set == kImageNotImageSet) || |
| (type == Resource::kImage && is_image_set == kImageIsImageSet)); |
| switch (type) { |
| case Resource::kMainResource: |
| if (!is_main_frame) |
| return WebURLRequest::kRequestContextIframe; |
| // FIXME: Change this to a context frame type (once we introduce them): |
| // http://fetch.spec.whatwg.org/#concept-request-context-frame-type |
| return WebURLRequest::kRequestContextHyperlink; |
| case Resource::kXSLStyleSheet: |
| DCHECK(RuntimeEnabledFeatures::XSLTEnabled()); |
| case Resource::kCSSStyleSheet: |
| return WebURLRequest::kRequestContextStyle; |
| case Resource::kScript: |
| return WebURLRequest::kRequestContextScript; |
| case Resource::kFont: |
| return WebURLRequest::kRequestContextFont; |
| case Resource::kImage: |
| if (is_image_set == kImageIsImageSet) |
| return WebURLRequest::kRequestContextImageSet; |
| return WebURLRequest::kRequestContextImage; |
| case Resource::kRaw: |
| return WebURLRequest::kRequestContextSubresource; |
| case Resource::kImportResource: |
| return WebURLRequest::kRequestContextImport; |
| case Resource::kLinkPrefetch: |
| return WebURLRequest::kRequestContextPrefetch; |
| case Resource::kTextTrack: |
| return WebURLRequest::kRequestContextTrack; |
| case Resource::kSVGDocument: |
| return WebURLRequest::kRequestContextImage; |
| case Resource::kMedia: // TODO: Split this. |
| return WebURLRequest::kRequestContextVideo; |
| case Resource::kManifest: |
| return WebURLRequest::kRequestContextManifest; |
| case Resource::kMock: |
| return WebURLRequest::kRequestContextSubresource; |
| } |
| NOTREACHED(); |
| return WebURLRequest::kRequestContextSubresource; |
| } |
| |
| ResourceFetcher::ResourceFetcher(FetchContext* new_context, |
| RefPtr<WebTaskRunner> task_runner) |
| : context_(new_context), |
| scheduler_(ResourceLoadScheduler::Create(&Context())), |
| archive_(Context().IsMainFrame() ? nullptr : Context().Archive()), |
| resource_timing_report_timer_( |
| std::move(task_runner), |
| this, |
| &ResourceFetcher::ResourceTimingReportTimerFired), |
| auto_load_images_(true), |
| images_enabled_(true), |
| allow_stale_resources_(false), |
| image_fetched_(false) {} |
| |
| ResourceFetcher::~ResourceFetcher() {} |
| |
| Resource* ResourceFetcher::CachedResource(const KURL& resource_url) const { |
| KURL url = MemoryCache::RemoveFragmentIdentifierIfNeeded(resource_url); |
| const WeakMember<Resource>& resource = document_resources_.at(url); |
| return resource.Get(); |
| } |
| |
| bool 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() == Resource::kFont && !params.IsLinkPreload()) |
| return false; |
| |
| // Defer loading images either when: |
| // - images are disabled |
| // - instructed to defer loading images from network |
| if (resource->GetType() == Resource::kImage && |
| ShouldDeferImageLoad(resource->Url())) |
| return false; |
| |
| return policy != kUse || resource->StillNeedsLoad(); |
| } |
| |
| // Limit the number of URLs in m_validatedURLs to avoid memory bloat. |
| // http://crbug.com/52411 |
| static const int kMaxValidatedURLsSize = 10000; |
| |
| void ResourceFetcher::RequestLoadStarted(unsigned long identifier, |
| Resource* resource, |
| const FetchParameters& params, |
| RevalidationPolicy policy, |
| bool is_static_data) { |
| if (policy == kUse && resource->GetStatus() == ResourceStatus::kCached && |
| !validated_urls_.Contains(resource->Url())) { |
| // Loaded from MemoryCache. |
| DidLoadResourceFromMemoryCache(identifier, resource, |
| params.GetResourceRequest()); |
| } |
| |
| if (is_static_data) |
| return; |
| |
| if (policy == kUse && !resource->StillNeedsLoad() && |
| !validated_urls_.Contains(params.GetResourceRequest().Url())) { |
| // Resources loaded from memory cache should be reported the first time |
| // they're used. |
| RefPtr<ResourceTimingInfo> info = ResourceTimingInfo::Create( |
| params.Options().initiator_info.name, MonotonicallyIncreasingTime(), |
| resource->GetType() == Resource::kMainResource); |
| PopulateTimingInfo(info.Get(), resource); |
| info->ClearLoadTimings(); |
| info->SetLoadFinishTime(info->InitialTime()); |
| scheduled_resource_timing_reports_.push_back(std::move(info)); |
| if (!resource_timing_report_timer_.IsActive()) |
| resource_timing_report_timer_.StartOneShot(0, BLINK_FROM_HERE); |
| } |
| |
| if (validated_urls_.size() >= kMaxValidatedURLsSize) { |
| validated_urls_.clear(); |
| } |
| validated_urls_.insert(params.GetResourceRequest().Url()); |
| } |
| |
| void ResourceFetcher::DidLoadResourceFromMemoryCache( |
| unsigned long identifier, |
| Resource* resource, |
| const ResourceRequest& original_resource_request) { |
| ResourceRequest resource_request(resource->Url()); |
| resource_request.SetFrameType(original_resource_request.GetFrameType()); |
| resource_request.SetRequestContext( |
| original_resource_request.GetRequestContext()); |
| Context().DispatchDidLoadResourceFromMemoryCache(identifier, resource_request, |
| resource->GetResponse()); |
| Context().DispatchWillSendRequest(identifier, resource_request, |
| ResourceResponse() /* redirects */, |
| resource->Options().initiator_info); |
| Context().DispatchDidReceiveResponse( |
| identifier, resource->GetResponse(), resource_request.GetFrameType(), |
| resource_request.GetRequestContext(), resource, |
| FetchContext::ResourceResponseType::kFromMemoryCache); |
| |
| if (resource->EncodedSize() > 0) |
| Context().DispatchDidReceiveData(identifier, 0, resource->EncodedSize()); |
| |
| Context().DispatchDidFinishLoading( |
| identifier, 0, 0, resource->GetResponse().DecodedBodyLength()); |
| } |
| |
| 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_); |
| |
| // TODO(japhet): We only send main resource data: urls through WebURLLoader |
| // for the benefit of a service worker test |
| // (RenderViewImplTest.ServiceWorkerNetworkProviderSetup), which is at a layer |
| // where it isn't easy to mock out a network load. It uses data: urls to |
| // emulate the behavior it wants to test, which would otherwise be reserved |
| // for network loads. |
| if (!archive_ && !substitute_data.IsValid() && |
| (factory.GetType() == Resource::kMainResource || |
| factory.GetType() == Resource::kRaw)) |
| return nullptr; |
| |
| const String cache_identifier = GetCacheIdentifier(); |
| 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; |
| RefPtr<SharedBuffer> data; |
| if (substitute_data.IsValid()) { |
| data = substitute_data.Content(); |
| response.SetURL(url); |
| response.SetMimeType(substitute_data.MimeType()); |
| response.SetExpectedContentLength(data->size()); |
| response.SetTextEncodingName(substitute_data.TextEncoding()); |
| } else if (url.ProtocolIsData()) { |
| data = NetworkUtils::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.SetURL(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->SetNeedsSynchronousCacheHit(substitute_data.ForceSynchronousLoad()); |
| // FIXME: We should provide a body stream here. |
| resource->SetStatus(ResourceStatus::kPending); |
| resource->NotifyStartLoad(); |
| resource->ResponseReceived(response, nullptr); |
| resource->SetDataBufferingPolicy(kBufferData); |
| if (data->size()) |
| resource->SetResourceBuffer(data); |
| resource->SetIdentifier(CreateUniqueIdentifier()); |
| resource->SetCacheIdentifier(cache_identifier); |
| resource->Finish(); |
| |
| if (ShouldResourceBeAddedToMemoryCache(params, resource) && |
| !substitute_data.IsValid()) { |
| GetMemoryCache()->Add(resource); |
| } |
| |
| return resource; |
| } |
| |
| Resource* ResourceFetcher::ResourceForBlockedRequest( |
| const FetchParameters& params, |
| const ResourceFactory& factory, |
| ResourceRequestBlockedReason blocked_reason) { |
| Resource* resource = factory.Create( |
| params.GetResourceRequest(), params.Options(), params.DecoderOptions()); |
| resource->SetStatus(ResourceStatus::kPending); |
| resource->NotifyStartLoad(); |
| resource->FinishAsError(ResourceError::CancelledDueToAccessCheckError( |
| params.Url(), blocked_reason)); |
| 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; |
| |
| 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); |
| } |
| |
| ResourceFetcher::PrepareRequestResult ResourceFetcher::PrepareRequest( |
| FetchParameters& params, |
| const ResourceFactory& factory, |
| const SubstituteData& substitute_data, |
| unsigned long identifier, |
| ResourceRequestBlockedReason& blocked_reason) { |
| ResourceRequest& resource_request = params.MutableResourceRequest(); |
| Resource::Type resource_type = factory.GetType(); |
| const ResourceLoaderOptions& options = params.Options(); |
| |
| DCHECK(options.synchronous_policy == kRequestAsynchronously || |
| resource_type == Resource::kRaw || |
| resource_type == Resource::kXSLStyleSheet); |
| |
| params.OverrideContentType(factory.ContentType()); |
| |
| // Don't send security violation reports for speculative preloads. |
| SecurityViolationReportingPolicy reporting_policy = |
| params.IsSpeculativePreload() |
| ? SecurityViolationReportingPolicy::kSuppressReporting |
| : SecurityViolationReportingPolicy::kReport; |
| |
| // 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 kAbort; |
| |
| resource_request.SetPriority(ComputeLoadPriority( |
| resource_type, params.GetResourceRequest(), ResourcePriority::kNotVisible, |
| params.Defer(), params.GetSpeculativePreloadType(), |
| params.IsLinkPreload())); |
| if (resource_request.GetCachePolicy() == |
| WebCachePolicy::kUseProtocolCachePolicy) { |
| resource_request.SetCachePolicy(Context().ResourceRequestCachePolicy( |
| resource_request, resource_type, params.Defer())); |
| } |
| if (resource_request.GetRequestContext() == |
| WebURLRequest::kRequestContextUnspecified) { |
| resource_request.SetRequestContext(DetermineRequestContext( |
| resource_type, kImageNotImageSet, Context().IsMainFrame())); |
| } |
| if (resource_type == Resource::kLinkPrefetch) |
| resource_request.SetHTTPHeaderField(HTTPNames::Purpose, "prefetch"); |
| |
| Context().AddAdditionalRequestHeaders( |
| resource_request, (resource_type == Resource::kMainResource) |
| ? kFetchMainResource |
| : kFetchSubresource); |
| |
| network_instrumentation::ResourcePrioritySet(identifier, |
| resource_request.Priority()); |
| |
| blocked_reason = Context().CanRequest( |
| resource_type, resource_request, |
| MemoryCache::RemoveFragmentIdentifierIfNeeded(params.Url()), options, |
| reporting_policy, params.GetOriginRestriction(), |
| resource_request.GetRedirectStatus()); |
| if (blocked_reason != ResourceRequestBlockedReason::kNone) { |
| DCHECK(!substitute_data.ForceSynchronousLoad()); |
| return kBlock; |
| } |
| |
| // For initial requests, call prepareRequest() here before revalidation |
| // policy is determined. |
| Context().PrepareRequest(resource_request, |
| FetchContext::RedirectType::kNotForRedirect); |
| |
| if (!params.Url().IsValid()) |
| return kAbort; |
| |
| RefPtr<SecurityOrigin> origin = options.security_origin; |
| params.MutableOptions().cors_flag = |
| !origin || !origin->CanRequestNoSuborigin(params.Url()); |
| |
| if (options.cors_handling_by_resource_fetcher == |
| kEnableCORSHandlingByResourceFetcher) { |
| bool allow_stored_credentials = false; |
| switch (resource_request.GetFetchCredentialsMode()) { |
| case WebURLRequest::kFetchCredentialsModeOmit: |
| break; |
| case WebURLRequest::kFetchCredentialsModeSameOrigin: |
| allow_stored_credentials = |
| !params.Options().cors_flag || |
| (origin && |
| origin->HasSuboriginAndShouldAllowCredentialsFor(params.Url())); |
| break; |
| case WebURLRequest::kFetchCredentialsModeInclude: |
| case WebURLRequest::kFetchCredentialsModePassword: |
| allow_stored_credentials = true; |
| break; |
| } |
| resource_request.SetAllowStoredCredentials(allow_stored_credentials); |
| } |
| |
| return kContinue; |
| } |
| |
| Resource* ResourceFetcher::RequestResource( |
| FetchParameters& params, |
| const ResourceFactory& factory, |
| const SubstituteData& substitute_data) { |
| unsigned long identifier = CreateUniqueIdentifier(); |
| ResourceRequest& resource_request = params.MutableResourceRequest(); |
| network_instrumentation::ScopedResourceLoadTracker |
| scoped_resource_load_tracker(identifier, resource_request); |
| SCOPED_BLINK_UMA_HISTOGRAM_TIMER("Blink.Fetch.RequestResourceTime"); |
| // TODO(dproy): Remove this. http://crbug.com/659666 |
| TRACE_EVENT1("blink", "ResourceFetcher::requestResource", "url", |
| UrlForTraceEvent(params.Url())); |
| |
| ResourceRequestBlockedReason blocked_reason = |
| ResourceRequestBlockedReason::kNone; |
| |
| PrepareRequestResult result = PrepareRequest(params, factory, substitute_data, |
| identifier, blocked_reason); |
| if (result == kAbort) |
| return nullptr; |
| if (result == kBlock) |
| return ResourceForBlockedRequest(params, factory, blocked_reason); |
| |
| Resource::Type resource_type = factory.GetType(); |
| |
| if (!params.IsSpeculativePreload()) { |
| // Only log if it's not for speculative preload. |
| Context().RecordLoadingActivity(identifier, 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_; |
| if (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 nullptr; |
| } |
| } |
| |
| if (!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); |
| // Fall through |
| case kLoad: |
| resource = CreateResourceForLoading(params, factory); |
| break; |
| case kRevalidate: |
| InitializeRevalidation(resource_request, resource); |
| break; |
| case kUse: |
| 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); |
| |
| // 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); |
| document_resources_.Set( |
| MemoryCache::RemoveFragmentIdentifierIfNeeded(params.Url()), 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 (policy != kUse) |
| InsertAsPreloadIfNecessary(resource, params, resource_type); |
| return resource; |
| } |
| |
| if (!StartLoad(resource)) |
| return nullptr; |
| |
| if (policy != kUse) |
| InsertAsPreloadIfNecessary(resource, params, resource_type); |
| scoped_resource_load_tracker.ResourceLoadContinuesBeyondScope(); |
| |
| DCHECK(!resource->ErrorOccurred() || |
| params.Options().synchronous_policy == kRequestSynchronously); |
| return resource; |
| } |
| |
| void ResourceFetcher::ResourceTimingReportTimerFired(TimerBase* timer) { |
| DCHECK_EQ(timer, &resource_timing_report_timer_); |
| Vector<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()); |
| // RawResource doesn't support revalidation. |
| CHECK(!IsRawResource(*resource)); |
| |
| const AtomicString& last_modified = |
| resource->GetResponse().HttpHeaderField(HTTPNames::Last_Modified); |
| const AtomicString& e_tag = |
| resource->GetResponse().HttpHeaderField(HTTPNames::ETag); |
| if (!last_modified.IsEmpty() || !e_tag.IsEmpty()) { |
| DCHECK_NE(WebCachePolicy::kBypassingCache, |
| revalidating_request.GetCachePolicy()); |
| if (revalidating_request.GetCachePolicy() == |
| WebCachePolicy::kValidatingCacheData) { |
| revalidating_request.SetHTTPHeaderField(HTTPNames::Cache_Control, |
| "max-age=0"); |
| } |
| } |
| if (!last_modified.IsEmpty()) { |
| revalidating_request.SetHTTPHeaderField(HTTPNames::If_Modified_Since, |
| last_modified); |
| } |
| if (!e_tag.IsEmpty()) |
| revalidating_request.SetHTTPHeaderField(HTTPNames::If_None_Match, e_tag); |
| |
| resource->SetRevalidatingRequest(revalidating_request); |
| } |
| |
| Resource* ResourceFetcher::CreateResourceForLoading( |
| FetchParameters& params, |
| const ResourceFactory& factory) { |
| const String cache_identifier = GetCacheIdentifier(); |
| DCHECK(!IsMainThread() || |
| !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()); |
| if (params.IsSpeculativePreload()) { |
| resource->SetPreloadDiscoveryTime(params.PreloadDiscoveryTime()); |
| } |
| resource->SetCacheIdentifier(cache_identifier); |
| |
| if (ShouldResourceBeAddedToMemoryCache(params, resource)) |
| GetMemoryCache()->Add(resource); |
| return resource; |
| } |
| |
| void ResourceFetcher::StorePerformanceTimingInitiatorInformation( |
| Resource* resource) { |
| const AtomicString& fetch_initiator = resource->Options().initiator_info.name; |
| if (fetch_initiator == FetchInitiatorTypeNames::internal) |
| return; |
| |
| bool is_main_resource = resource->GetType() == Resource::kMainResource; |
| |
| // The request can already be fetched in a previous navigation. Thus |
| // startTime must be set accordingly. |
| double start_time = resource->GetResourceRequest().NavigationStartTime() |
| ? resource->GetResourceRequest().NavigationStartTime() |
| : MonotonicallyIncreasingTime(); |
| |
| // This buffer is created and populated for providing transferSize |
| // and redirect timing opt-in information. |
| if (is_main_resource) { |
| DCHECK(!navigation_timing_info_); |
| navigation_timing_info_ = ResourceTimingInfo::Create( |
| fetch_initiator, start_time, is_main_resource); |
| } |
| |
| RefPtr<ResourceTimingInfo> info = |
| ResourceTimingInfo::Create(fetch_initiator, start_time, is_main_resource); |
| |
| if (resource->IsCacheValidator()) { |
| const AtomicString& timing_allow_origin = |
| resource->GetResponse().HttpHeaderField(HTTPNames::Timing_Allow_Origin); |
| if (!timing_allow_origin.IsEmpty()) |
| info->SetOriginalTimingAllowOrigin(timing_allow_origin); |
| } |
| |
| if (!is_main_resource || |
| Context().UpdateTimingInfoForIFrameNavigation(info.Get())) { |
| resource_timing_info_map_.insert(resource, std::move(info)); |
| } |
| } |
| |
| void ResourceFetcher::RecordResourceTimingOnRedirect( |
| Resource* resource, |
| const ResourceResponse& redirect_response, |
| bool cross_origin) { |
| ResourceTimingInfoMap::iterator it = resource_timing_info_map_.find(resource); |
| if (it != resource_timing_info_map_.end()) { |
| it->value->AddRedirect(redirect_response, cross_origin); |
| } |
| |
| if (resource->GetType() == Resource::kMainResource) { |
| DCHECK(navigation_timing_info_); |
| navigation_timing_info_->AddRedirect(redirect_response, cross_origin); |
| } |
| } |
| |
| static bool IsDownloadOrStreamRequest(const ResourceRequest& request) { |
| // Never use cache entries for DownloadToFile / UseStreamOnResponse requests. |
| // The data will be delivered through other paths. |
| return request.DownloadToFile() || request.UseStreamOnResponse(); |
| } |
| |
| Resource* ResourceFetcher::MatchPreload(const FetchParameters& params, |
| Resource::Type type) { |
| auto it = preloads_.find(PreloadKey(params.Url(), type)); |
| if (it == preloads_.end()) |
| return nullptr; |
| |
| Resource* resource = it->value; |
| |
| if (resource->MustRefetchDueToIntegrityMetadata(params)) |
| return nullptr; |
| |
| if (params.IsSpeculativePreload()) |
| return resource; |
| if (params.IsLinkPreload()) { |
| resource->SetLinkPreload(true); |
| return resource; |
| } |
| |
| const ResourceRequest& request = params.GetResourceRequest(); |
| if (IsDownloadOrStreamRequest(request)) |
| return nullptr; |
| |
| if (IsImageResourceDisallowedToBeReused(*resource) || |
| !resource->CanReuse(params)) |
| return nullptr; |
| |
| resource->MatchPreload(); |
| preloads_.erase(it); |
| matched_preloads_.push_back(resource); |
| return resource; |
| } |
| |
| void ResourceFetcher::InsertAsPreloadIfNecessary(Resource* resource, |
| const FetchParameters& params, |
| Resource::Type type) { |
| if (!params.IsSpeculativePreload() && !params.IsLinkPreload()) |
| return; |
| // CSP layout 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->GetResourceError().IsAccessCheck()) |
| return; |
| PreloadKey key(params.Url(), type); |
| if (preloads_.find(key) == preloads_.end()) { |
| 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() != Resource::kImage) |
| return false; |
| |
| return !Context().AllowImage(images_enabled_, existing_resource.Url()); |
| } |
| |
| ResourceFetcher::RevalidationPolicy |
| ResourceFetcher::DetermineRevalidationPolicy( |
| Resource::Type 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( |
| Resource::Type 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_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; |
| |
| // WebCachePolicy::ReturnCacheDataElseLoad uses the cache no matter what. |
| if (request.GetCachePolicy() == WebCachePolicy::kReturnCacheDataElseLoad) |
| 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 != Resource::kRaw) { |
| if (!Context().IsLoadComplete() && |
| validated_urls_.Contains(existing_resource.Url())) |
| return kUse; |
| if (existing_resource.IsLoading()) |
| return kUse; |
| } |
| |
| // WebCachePolicy::BypassingCache always reloads |
| if (request.GetCachePolicy() == WebCachePolicy::kBypassingCache) { |
| RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy " |
| "reloading due to " |
| "WebCachePolicy::BypassingCache."; |
| 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 == Resource::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.GetCachePolicy() == WebCachePolicy::kValidatingCacheData || |
| existing_resource.MustRevalidateDueToCacheHeaders() || |
| 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) |
| if (existing_resource.CanUseCacheValidator() && |
| !Context().IsControlledByServiceWorker()) { |
| // 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_.Values()) { |
| if (resource->GetType() == Resource::kImage && resource->StillNeedsLoad() && |
| !ShouldDeferImageLoad(resource->Url())) |
| StartLoad(resource); |
| } |
| } |
| |
| void ResourceFetcher::ClearContext() { |
| scheduler_->Shutdown(); |
| ClearPreloads(ResourceFetcher::kClearAllPreloads); |
| context_ = Context().Detach(); |
| } |
| |
| int ResourceFetcher::BlockingRequestCount() const { |
| return loaders_.size(); |
| } |
| |
| int ResourceFetcher::NonblockingRequestCount() const { |
| return non_blocking_loaders_.size(); |
| } |
| |
| void ResourceFetcher::EnableIsPreloadedForTest() { |
| if (preloaded_urls_for_test_) |
| return; |
| preloaded_urls_for_test_ = WTF::WrapUnique(new 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) { |
| LogPreloadStats(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(); |
| } |
| |
| void ResourceFetcher::WarnUnusedPreloads() { |
| for (const auto& pair : preloads_) { |
| Resource* resource = pair.value; |
| if (resource && resource->IsLinkPreload() && resource->IsUnusedPreload()) { |
| Context().AddWarningConsoleMessage( |
| "The resource " + resource->Url().GetString() + |
| " was preloaded using link preload but not used within a few " |
| "seconds from the window's load event. Please make sure it " |
| "wasn't preloaded for nothing.", |
| FetchContext::kJSSource); |
| } |
| } |
| } |
| |
| ArchiveResource* ResourceFetcher::CreateArchive(Resource* resource) { |
| // Only the top-frame can load MHTML. |
| if (!Context().IsMainFrame()) { |
| Context().AddErrorConsoleMessage( |
| "Attempted to load a multipart archive into an subframe: " + |
| resource->Url().GetString(), |
| FetchContext::kJSSource); |
| return nullptr; |
| } |
| |
| archive_ = MHTMLArchive::Create(resource->Url(), resource->ResourceBuffer()); |
| if (!archive_) { |
| // Log if attempting to load an invalid archive resource. |
| Context().AddErrorConsoleMessage( |
| "Malformed multipart archive: " + resource->Url().GetString(), |
| FetchContext::kJSSource); |
| return nullptr; |
| } |
| |
| return archive_->MainResource(); |
| } |
| |
| ResourceTimingInfo* ResourceFetcher::GetNavigationTimingInfo() { |
| return navigation_timing_info_.Get(); |
| } |
| |
| void ResourceFetcher::HandleLoadCompletion(Resource* resource) { |
| Context().DidLoadResource(resource); |
| |
| resource->ReloadIfLoFiOrPlaceholderImage(this, Resource::kReloadIfNeeded); |
| } |
| |
| void ResourceFetcher::HandleLoaderFinish(Resource* resource, |
| double finish_time, |
| LoaderFinishType type) { |
| DCHECK(resource); |
| |
| 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 (resource->GetType() == Resource::kMainResource) { |
| DCHECK(navigation_timing_info_); |
| // Store redirect responses that were packed inside the final response. |
| AddRedirectsToTimingInfo(resource, navigation_timing_info_.Get()); |
| if (resource->GetResponse().IsHTTP()) { |
| PopulateTimingInfo(navigation_timing_info_.Get(), resource); |
| navigation_timing_info_->AddFinalTransferSize( |
| encoded_data_length == -1 ? 0 : encoded_data_length); |
| } |
| } |
| if (RefPtr<ResourceTimingInfo> info = |
| resource_timing_info_map_.Take(resource)) { |
| // Store redirect responses that were packed inside the final response. |
| AddRedirectsToTimingInfo(resource, info.Get()); |
| |
| if (resource->GetResponse().IsHTTP() && |
| resource->GetResponse().HttpStatusCode() < 400) { |
| PopulateTimingInfo(info.Get(), resource); |
| 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); |
| } |
| } |
| |
| Context().DispatchDidFinishLoading( |
| resource->Identifier(), finish_time, encoded_data_length, |
| resource->GetResponse().DecodedBodyLength()); |
| |
| if (type == kDidFinishLoading) |
| resource->Finish(finish_time); |
| |
| HandleLoadCompletion(resource); |
| } |
| |
| void ResourceFetcher::HandleLoaderError(Resource* resource, |
| const ResourceError& error) { |
| DCHECK(resource); |
| |
| RemoveResourceLoader(resource->Loader()); |
| |
| resource_timing_info_map_.Take(resource); |
| |
| bool is_internal_request = resource->Options().initiator_info.name == |
| FetchInitiatorTypeNames::internal; |
| |
| Context().DispatchDidFail(resource->Identifier(), error, |
| resource->GetResponse().EncodedDataLength(), |
| is_internal_request); |
| |
| if (error.IsCancellation()) |
| RemovePreload(resource); |
| resource->FinishAsError(error); |
| |
| HandleLoadCompletion(resource); |
| } |
| |
| void ResourceFetcher::MoveResourceLoaderToNonBlocking(ResourceLoader* loader) { |
| DCHECK(loader); |
| // TODO(yoav): Convert CHECK to DCHECK if no crash reports come in. |
| CHECK(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); |
| ScriptForbiddenIfMainThreadScope 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); |
| |
| Context().DispatchWillSendRequest(resource->Identifier(), request, response, |
| resource->Options().initiator_info); |
| |
| // Resource requests from suborigins should not be intercepted by the |
| // service worker of the physical origin. This has the effect that, for now, |
| // suborigins do not work with service workers. See |
| // https://w3c.github.io/webappsec-suborigins/. |
| SecurityOrigin* source_origin = Context().GetSecurityOrigin(); |
| if (source_origin && source_origin->HasSuborigin()) |
| request.SetServiceWorkerMode(WebURLRequest::ServiceWorkerMode::kNone); |
| |
| // TODO(shaochuan): Saving modified ResourceRequest back to |resource|, |
| // remove once dispatchWillSendRequest() takes const ResourceRequest. |
| // crbug.com/632580 |
| resource->SetResourceRequest(request); |
| |
| loader = ResourceLoader::Create(this, scheduler_, resource); |
| if (resource->ShouldBlockLoadEvent()) |
| loaders_.insert(loader); |
| else |
| non_blocking_loaders_.insert(loader); |
| |
| StorePerformanceTimingInitiatorInformation(resource); |
| resource->SetFetcherSecurityOrigin(source_origin); |
| |
| // NotifyStartLoad() shouldn't cause AddClient/RemoveClient(). |
| Resource::ProhibitAddRemoveClientInScope |
| prohibit_add_remove_client_in_scope(resource); |
| |
| resource->NotifyStartLoad(); |
| } |
| |
| loader->Start(); |
| 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(); |
| } |
| |
| void ResourceFetcher::StopFetching() { |
| // 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 (!loader->GetKeepalive()) |
| loaders_to_cancel.push_back(loader); |
| } |
| for (const auto& loader : loaders_) { |
| if (!loader->GetKeepalive()) |
| 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::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 (const auto& document_resource : document_resources_) { |
| Resource* resource = document_resource.value.Get(); |
| if (!resource || resource->GetType() != Resource::kImage || |
| !resource->IsLoading()) |
| continue; |
| |
| ResourcePriority resource_priority = resource->PriorityFromObservers(); |
| ResourceLoadPriority resource_load_priority = |
| ComputeLoadPriority(Resource::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 (const auto& document_resource : document_resources_) { |
| Resource* resource = document_resource.value.Get(); |
| if (resource) |
| resource->ReloadIfLoFiOrPlaceholderImage(this, Resource::kReloadAlways); |
| } |
| } |
| |
| void ResourceFetcher::LogPreloadStats(ClearPreloadsPolicy policy) { |
| unsigned scripts = 0; |
| unsigned script_misses = 0; |
| unsigned stylesheets = 0; |
| unsigned stylesheet_misses = 0; |
| unsigned images = 0; |
| unsigned image_misses = 0; |
| unsigned fonts = 0; |
| unsigned font_misses = 0; |
| unsigned medias = 0; |
| unsigned media_misses = 0; |
| unsigned text_tracks = 0; |
| unsigned text_track_misses = 0; |
| unsigned imports = 0; |
| unsigned import_misses = 0; |
| unsigned raws = 0; |
| unsigned raw_misses = 0; |
| for (const auto& pair : preloads_) { |
| Resource* resource = pair.value; |
| // Do not double count link rel preloads. These do not get cleared if the |
| // ClearPreloadsPolicy is only clearing speculative markup preloads. |
| if (resource->IsLinkPreload() && |
| policy == kClearSpeculativeMarkupPreloads) { |
| continue; |
| } |
| int miss_count = resource->IsUnusedPreload() ? 1 : 0; |
| switch (resource->GetType()) { |
| case Resource::kImage: |
| images++; |
| image_misses += miss_count; |
| break; |
| case Resource::kScript: |
| scripts++; |
| script_misses += miss_count; |
| break; |
| case Resource::kCSSStyleSheet: |
| stylesheets++; |
| stylesheet_misses += miss_count; |
| break; |
| case Resource::kFont: |
| fonts++; |
| font_misses += miss_count; |
| break; |
| case Resource::kMedia: |
| medias++; |
| media_misses += miss_count; |
| break; |
| case Resource::kTextTrack: |
| text_tracks++; |
| text_track_misses += miss_count; |
| break; |
| case Resource::kImportResource: |
| imports++; |
| import_misses += miss_count; |
| break; |
| case Resource::kRaw: |
| raws++; |
| raw_misses += miss_count; |
| break; |
| case Resource::kMock: |
| // Do not count Resource::Mock because this type is only for testing. |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, image_preloads, |
| ("PreloadScanner.Counts2.Image", 0, 100, 25)); |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, image_preload_misses, |
| ("PreloadScanner.Counts2.Miss.Image", 0, 100, 25)); |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, script_preloads, |
| ("PreloadScanner.Counts2.Script", 0, 100, 25)); |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, script_preload_misses, |
| ("PreloadScanner.Counts2.Miss.Script", 0, 100, 25)); |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, stylesheet_preloads, |
| ("PreloadScanner.Counts2.CSSStyleSheet", 0, 100, 25)); |
| DEFINE_STATIC_LOCAL( |
| CustomCountHistogram, stylesheet_preload_misses, |
| ("PreloadScanner.Counts2.Miss.CSSStyleSheet", 0, 100, 25)); |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, font_preloads, |
| ("PreloadScanner.Counts2.Font", 0, 100, 25)); |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, font_preload_misses, |
| ("PreloadScanner.Counts2.Miss.Font", 0, 100, 25)); |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, media_preloads, |
| ("PreloadScanner.Counts2.Media", 0, 100, 25)); |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, media_preload_misses, |
| ("PreloadScanner.Counts2.Miss.Media", 0, 100, 25)); |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, text_track_preloads, |
| ("PreloadScanner.Counts2.TextTrack", 0, 100, 25)); |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, text_track_preload_misses, |
| ("PreloadScanner.Counts2.Miss.TextTrack", 0, 100, 25)); |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, import_preloads, |
| ("PreloadScanner.Counts2.Import", 0, 100, 25)); |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, import_preload_misses, |
| ("PreloadScanner.Counts2.Miss.Import", 0, 100, 25)); |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, raw_preloads, |
| ("PreloadScanner.Counts2.Raw", 0, 100, 25)); |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, raw_preload_misses, |
| ("PreloadScanner.Counts2.Miss.Raw", 0, 100, 25)); |
| if (images) |
| image_preloads.Count(images); |
| if (image_misses) |
| image_preload_misses.Count(image_misses); |
| if (scripts) |
| script_preloads.Count(scripts); |
| if (script_misses) |
| script_preload_misses.Count(script_misses); |
| if (stylesheets) |
| stylesheet_preloads.Count(stylesheets); |
| if (stylesheet_misses) |
| stylesheet_preload_misses.Count(stylesheet_misses); |
| if (fonts) |
| font_preloads.Count(fonts); |
| if (font_misses) |
| font_preload_misses.Count(font_misses); |
| if (medias) |
| media_preloads.Count(medias); |
| if (media_misses) |
| media_preload_misses.Count(media_misses); |
| if (text_tracks) |
| text_track_preloads.Count(text_tracks); |
| if (text_track_misses) |
| text_track_preload_misses.Count(text_track_misses); |
| if (imports) |
| import_preloads.Count(imports); |
| if (import_misses) |
| import_preload_misses.Count(import_misses); |
| if (raws) |
| raw_preloads.Count(raws); |
| if (raw_misses) |
| raw_preload_misses.Count(raw_misses); |
| } |
| |
| String ResourceFetcher::GetCacheIdentifier() const { |
| if (Context().IsControlledByServiceWorker()) |
| return String::Number(Context().ServiceWorkerID()); |
| return MemoryCache::DefaultCacheIdentifier(); |
| } |
| |
| void ResourceFetcher::EmulateLoadStartedForInspector( |
| Resource* resource, |
| const KURL& url, |
| WebURLRequest::RequestContext 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, |
| params.GetOriginRestriction(), |
| resource->LastResourceRequest().GetRedirectStatus()); |
| RequestLoadStarted(resource->Identifier(), resource, params, kUse); |
| } |
| |
| DEFINE_TRACE(ResourceFetcher) { |
| visitor->Trace(context_); |
| visitor->Trace(scheduler_); |
| visitor->Trace(archive_); |
| visitor->Trace(loaders_); |
| visitor->Trace(non_blocking_loaders_); |
| visitor->Trace(document_resources_); |
| visitor->Trace(preloads_); |
| visitor->Trace(matched_preloads_); |
| visitor->Trace(resource_timing_info_map_); |
| } |
| |
| STATIC_ASSERT_ENUM(WebURLRequest::kPriorityUnresolved, |
| kResourceLoadPriorityUnresolved); |
| STATIC_ASSERT_ENUM(WebURLRequest::kPriorityVeryLow, |
| kResourceLoadPriorityVeryLow); |
| STATIC_ASSERT_ENUM(WebURLRequest::kPriorityLow, kResourceLoadPriorityLow); |
| STATIC_ASSERT_ENUM(WebURLRequest::kPriorityMedium, kResourceLoadPriorityMedium); |
| STATIC_ASSERT_ENUM(WebURLRequest::kPriorityHigh, kResourceLoadPriorityHigh); |
| STATIC_ASSERT_ENUM(WebURLRequest::kPriorityVeryHigh, |
| kResourceLoadPriorityVeryHigh); |
| |
| } // namespace blink |