| /* |
| 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 Apple Inc. All rights reserved. |
| |
| 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. |
| */ |
| |
| #include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h" |
| |
| #include "base/auto_reset.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_loading_log.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_origin.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_origin_hash.h" |
| #include "third_party/blink/renderer/platform/wtf/assertions.h" |
| #include "third_party/blink/renderer/platform/wtf/math_extras.h" |
| #include "third_party/blink/renderer/platform/wtf/text/cstring.h" |
| #include "third_party/blink/renderer/platform/wtf/time.h" |
| |
| namespace blink { |
| |
| static Persistent<MemoryCache>* g_memory_cache; |
| |
| static const unsigned kCDefaultCacheCapacity = 8192 * 1024; |
| static const int kCMinDelayBeforeLiveDecodedPrune = 1; // Seconds. |
| static const double kCMaxPruneDeferralDelay = 0.5; // Seconds. |
| |
| // Percentage of capacity toward which we prune, to avoid immediately pruning |
| // again. |
| static const float kCTargetPrunePercentage = .95f; |
| |
| MemoryCache* GetMemoryCache() { |
| DCHECK(WTF::IsMainThread()); |
| if (!g_memory_cache) |
| g_memory_cache = new Persistent<MemoryCache>(MemoryCache::Create()); |
| return g_memory_cache->Get(); |
| } |
| |
| MemoryCache* ReplaceMemoryCacheForTesting(MemoryCache* cache) { |
| GetMemoryCache(); |
| MemoryCache* old_cache = g_memory_cache->Release(); |
| *g_memory_cache = cache; |
| MemoryCacheDumpProvider::Instance()->SetMemoryCache(cache); |
| return old_cache; |
| } |
| |
| void MemoryCacheEntry::Trace(blink::Visitor* visitor) { |
| visitor->template RegisterWeakMembers<MemoryCacheEntry, |
| &MemoryCacheEntry::ClearResourceWeak>( |
| this); |
| } |
| |
| void MemoryCacheEntry::ClearResourceWeak(Visitor* visitor) { |
| if (!resource_ || ThreadHeap::IsHeapObjectAlive(resource_)) |
| return; |
| GetMemoryCache()->Remove(resource_.Get()); |
| resource_.Clear(); |
| } |
| |
| inline MemoryCache::MemoryCache() |
| : in_prune_resources_(false), |
| prune_pending_(false), |
| max_prune_deferral_delay_(kCMaxPruneDeferralDelay), |
| prune_time_stamp_(0.0), |
| prune_frame_time_stamp_(0.0), |
| last_frame_paint_time_stamp_(0.0), |
| capacity_(kCDefaultCacheCapacity), |
| delay_before_live_decoded_prune_(kCMinDelayBeforeLiveDecodedPrune), |
| size_(0) { |
| MemoryCacheDumpProvider::Instance()->SetMemoryCache(this); |
| if (MemoryCoordinator::IsLowEndDevice()) |
| MemoryCoordinator::Instance().RegisterClient(this); |
| } |
| |
| MemoryCache* MemoryCache::Create() { |
| return new MemoryCache; |
| } |
| |
| MemoryCache::~MemoryCache() { |
| if (prune_pending_) |
| Platform::Current()->CurrentThread()->RemoveTaskObserver(this); |
| } |
| |
| void MemoryCache::Trace(blink::Visitor* visitor) { |
| visitor->Trace(resource_maps_); |
| MemoryCacheDumpClient::Trace(visitor); |
| MemoryCoordinatorClient::Trace(visitor); |
| } |
| |
| KURL MemoryCache::RemoveFragmentIdentifierIfNeeded(const KURL& original_url) { |
| if (!original_url.HasFragmentIdentifier()) |
| return original_url; |
| // Strip away fragment identifier from HTTP URLs. Data URLs must be |
| // unmodified. For file and custom URLs clients may expect resources to be |
| // unique even when they differ by the fragment identifier only. |
| if (!original_url.ProtocolIsInHTTPFamily()) |
| return original_url; |
| KURL url = original_url; |
| url.RemoveFragmentIdentifier(); |
| return url; |
| } |
| |
| String MemoryCache::DefaultCacheIdentifier() { |
| return g_empty_string; |
| } |
| |
| MemoryCache::ResourceMap* MemoryCache::EnsureResourceMap( |
| const String& cache_identifier) { |
| if (!resource_maps_.Contains(cache_identifier)) { |
| ResourceMapIndex::AddResult result = |
| resource_maps_.insert(cache_identifier, new ResourceMap); |
| CHECK(result.is_new_entry); |
| } |
| return resource_maps_.at(cache_identifier); |
| } |
| |
| void MemoryCache::Add(Resource* resource) { |
| DCHECK(resource); |
| ResourceMap* resources = EnsureResourceMap(resource->CacheIdentifier()); |
| AddInternal(resources, MemoryCacheEntry::Create(resource)); |
| RESOURCE_LOADING_DVLOG(1) |
| << "MemoryCache::add Added " << resource->Url().GetString() |
| << ", resource " << resource; |
| } |
| |
| void MemoryCache::AddInternal(ResourceMap* resource_map, |
| MemoryCacheEntry* entry) { |
| DCHECK(WTF::IsMainThread()); |
| DCHECK(resource_map); |
| |
| Resource* resource = entry->GetResource(); |
| if (!resource) |
| return; |
| DCHECK(resource->Url().IsValid()); |
| |
| KURL url = RemoveFragmentIdentifierIfNeeded(resource->Url()); |
| ResourceMap::iterator it = resource_map->find(url); |
| if (it != resource_map->end()) { |
| Resource* old_resource = it->value->GetResource(); |
| CHECK_NE(old_resource, resource); |
| Update(old_resource, old_resource->size(), 0); |
| } |
| resource_map->Set(url, entry); |
| Update(resource, 0, resource->size()); |
| } |
| |
| void MemoryCache::Remove(Resource* resource) { |
| DCHECK(WTF::IsMainThread()); |
| DCHECK(resource); |
| // Resources can be created with garbage urls in error cases. These Resources |
| // should never be added to the cache (AddInternal() DCHECKs that the url is |
| // valid). Null urls will crash if we attempt to hash them, so early exit. |
| if (resource->Url().IsNull()) |
| return; |
| |
| RESOURCE_LOADING_DVLOG(1) << "Evicting resource " << resource << " for " |
| << resource->Url().GetString() << " from cache"; |
| TRACE_EVENT1("blink", "MemoryCache::evict", "resource", |
| resource->Url().GetString().Utf8()); |
| |
| ResourceMap* resources = resource_maps_.at(resource->CacheIdentifier()); |
| if (!resources) |
| return; |
| |
| KURL url = RemoveFragmentIdentifierIfNeeded(resource->Url()); |
| ResourceMap::iterator it = resources->find(url); |
| if (it == resources->end() || it->value->GetResource() != resource) |
| return; |
| RemoveInternal(resources, it); |
| } |
| |
| void MemoryCache::RemoveInternal(ResourceMap* resource_map, |
| const ResourceMap::iterator& it) { |
| DCHECK(WTF::IsMainThread()); |
| DCHECK(resource_map); |
| |
| Resource* resource = it->value->GetResource(); |
| DCHECK(resource); |
| |
| Update(resource, resource->size(), 0); |
| resource_map->erase(it); |
| } |
| |
| bool MemoryCache::Contains(const Resource* resource) const { |
| if (!resource || resource->Url().IsEmpty()) |
| return false; |
| const ResourceMap* resources = resource_maps_.at(resource->CacheIdentifier()); |
| if (!resources) |
| return false; |
| KURL url = RemoveFragmentIdentifierIfNeeded(resource->Url()); |
| MemoryCacheEntry* entry = resources->at(url); |
| return entry && resource == entry->GetResource(); |
| } |
| |
| Resource* MemoryCache::ResourceForURL(const KURL& resource_url) const { |
| return ResourceForURL(resource_url, DefaultCacheIdentifier()); |
| } |
| |
| Resource* MemoryCache::ResourceForURL(const KURL& resource_url, |
| const String& cache_identifier) const { |
| DCHECK(WTF::IsMainThread()); |
| if (!resource_url.IsValid() || resource_url.IsNull()) |
| return nullptr; |
| DCHECK(!cache_identifier.IsNull()); |
| const ResourceMap* resources = resource_maps_.at(cache_identifier); |
| if (!resources) |
| return nullptr; |
| MemoryCacheEntry* entry = |
| resources->at(RemoveFragmentIdentifierIfNeeded(resource_url)); |
| if (!entry) |
| return nullptr; |
| return entry->GetResource(); |
| } |
| |
| HeapVector<Member<Resource>> MemoryCache::ResourcesForURL( |
| const KURL& resource_url) const { |
| DCHECK(WTF::IsMainThread()); |
| KURL url = RemoveFragmentIdentifierIfNeeded(resource_url); |
| HeapVector<Member<Resource>> results; |
| for (const auto& resource_map_iter : resource_maps_) { |
| if (MemoryCacheEntry* entry = resource_map_iter.value->at(url)) { |
| Resource* resource = entry->GetResource(); |
| DCHECK(resource); |
| results.push_back(resource); |
| } |
| } |
| return results; |
| } |
| |
| void MemoryCache::PruneResources(PruneStrategy strategy) { |
| DCHECK(!prune_pending_); |
| const size_t size_limit = (strategy == kMaximalPrune) ? 0 : Capacity(); |
| if (size_ <= size_limit) |
| return; |
| |
| // Cut by a percentage to avoid immediately pruning again. |
| size_t target_size = |
| static_cast<size_t>(size_limit * kCTargetPrunePercentage); |
| |
| for (const auto& resource_map_iter : resource_maps_) { |
| for (const auto& resource_iter : *resource_map_iter.value) { |
| Resource* resource = resource_iter.value->GetResource(); |
| DCHECK(resource); |
| if (resource->IsLoaded() && resource->DecodedSize()) { |
| // Check to see if the remaining resources are too new to prune. |
| double elapsed_time = prune_frame_time_stamp_ - |
| resource_iter.value->last_decoded_access_time_; |
| if (strategy == kAutomaticPrune && |
| elapsed_time < delay_before_live_decoded_prune_) |
| continue; |
| resource->Prune(); |
| if (size_ <= target_size) |
| return; |
| } |
| } |
| } |
| } |
| |
| void MemoryCache::SetCapacity(size_t total_bytes) { |
| capacity_ = total_bytes; |
| Prune(); |
| } |
| |
| void MemoryCache::Update(Resource* resource, size_t old_size, size_t new_size) { |
| if (!Contains(resource)) |
| return; |
| ptrdiff_t delta = new_size - old_size; |
| DCHECK(delta >= 0 || size_ >= static_cast<size_t>(-delta)); |
| size_ += delta; |
| } |
| |
| void MemoryCache::RemoveURLFromCache(const KURL& url) { |
| HeapVector<Member<Resource>> resources = ResourcesForURL(url); |
| for (Resource* resource : resources) |
| Remove(resource); |
| } |
| |
| void MemoryCache::TypeStatistic::AddResource(Resource* o) { |
| count++; |
| size += o->size(); |
| decoded_size += o->DecodedSize(); |
| encoded_size += o->EncodedSize(); |
| overhead_size += o->OverheadSize(); |
| encoded_size_duplicated_in_data_urls += |
| o->Url().ProtocolIsData() ? o->EncodedSize() : 0; |
| } |
| |
| MemoryCache::Statistics MemoryCache::GetStatistics() const { |
| Statistics stats; |
| for (const auto& resource_map_iter : resource_maps_) { |
| for (const auto& resource_iter : *resource_map_iter.value) { |
| Resource* resource = resource_iter.value->GetResource(); |
| DCHECK(resource); |
| switch (resource->GetType()) { |
| case ResourceType::kImage: |
| stats.images.AddResource(resource); |
| break; |
| case ResourceType::kCSSStyleSheet: |
| stats.css_style_sheets.AddResource(resource); |
| break; |
| case ResourceType::kScript: |
| stats.scripts.AddResource(resource); |
| break; |
| case ResourceType::kXSLStyleSheet: |
| stats.xsl_style_sheets.AddResource(resource); |
| break; |
| case ResourceType::kFont: |
| stats.fonts.AddResource(resource); |
| break; |
| default: |
| stats.other.AddResource(resource); |
| break; |
| } |
| } |
| } |
| return stats; |
| } |
| |
| void MemoryCache::EvictResources() { |
| for (auto resource_map_iter = resource_maps_.begin(); |
| resource_map_iter != resource_maps_.end();) { |
| ResourceMap* resources = resource_map_iter->value.Get(); |
| for (auto resource_iter = resources->begin(); |
| resource_iter != resources->end(); |
| resource_iter = resources->begin()) { |
| DCHECK(resource_iter.Get()); |
| DCHECK(resource_iter->value.Get()); |
| DCHECK(resource_iter->value->GetResource()); |
| Resource* resource = resource_iter->value->GetResource(); |
| DCHECK(resource); |
| RemoveInternal(resources, resource_iter); |
| } |
| resource_maps_.erase(resource_map_iter); |
| resource_map_iter = resource_maps_.begin(); |
| } |
| } |
| |
| void MemoryCache::Prune() { |
| TRACE_EVENT0("renderer", "MemoryCache::prune()"); |
| |
| if (in_prune_resources_) |
| return; |
| if (size_ <= capacity_) // Fast path. |
| return; |
| |
| // To avoid burdening the current thread with repetitive pruning jobs, pruning |
| // is postponed until the end of the current task. If it has been more than |
| // m_maxPruneDeferralDelay since the last prune, then we prune immediately. If |
| // the current thread's run loop is not active, then pruning will happen |
| // immediately only if it has been over m_maxPruneDeferralDelay since the last |
| // prune. |
| double current_time = WTF::CurrentTime(); |
| if (prune_pending_) { |
| if (current_time - prune_time_stamp_ >= max_prune_deferral_delay_) { |
| PruneNow(current_time, kAutomaticPrune); |
| } |
| } else { |
| if (current_time - prune_time_stamp_ >= max_prune_deferral_delay_) { |
| PruneNow(current_time, kAutomaticPrune); // Delay exceeded, prune now. |
| } else { |
| // Defer. |
| Platform::Current()->CurrentThread()->AddTaskObserver(this); |
| prune_pending_ = true; |
| } |
| } |
| } |
| |
| void MemoryCache::WillProcessTask() {} |
| |
| void MemoryCache::DidProcessTask() { |
| // Perform deferred pruning |
| DCHECK(prune_pending_); |
| PruneNow(WTF::CurrentTime(), kAutomaticPrune); |
| } |
| |
| void MemoryCache::PruneAll() { |
| double current_time = WTF::CurrentTime(); |
| PruneNow(current_time, kMaximalPrune); |
| } |
| |
| void MemoryCache::PruneNow(double current_time, PruneStrategy strategy) { |
| if (prune_pending_) { |
| prune_pending_ = false; |
| Platform::Current()->CurrentThread()->RemoveTaskObserver(this); |
| } |
| |
| base::AutoReset<bool> reentrancy_protector(&in_prune_resources_, true); |
| |
| PruneResources(strategy); |
| prune_frame_time_stamp_ = last_frame_paint_time_stamp_; |
| prune_time_stamp_ = current_time; |
| } |
| |
| void MemoryCache::UpdateFramePaintTimestamp() { |
| last_frame_paint_time_stamp_ = CurrentTime(); |
| } |
| |
| bool MemoryCache::OnMemoryDump(WebMemoryDumpLevelOfDetail level_of_detail, |
| WebProcessMemoryDump* memory_dump) { |
| if (level_of_detail == WebMemoryDumpLevelOfDetail::kBackground) { |
| Statistics stats = GetStatistics(); |
| WebMemoryAllocatorDump* dump1 = |
| memory_dump->CreateMemoryAllocatorDump("web_cache/Image_resources"); |
| dump1->AddScalar("size", "bytes", |
| stats.images.encoded_size + stats.images.overhead_size); |
| WebMemoryAllocatorDump* dump2 = memory_dump->CreateMemoryAllocatorDump( |
| "web_cache/CSS stylesheet_resources"); |
| dump2->AddScalar("size", "bytes", |
| stats.css_style_sheets.encoded_size + |
| stats.css_style_sheets.overhead_size); |
| WebMemoryAllocatorDump* dump3 = |
| memory_dump->CreateMemoryAllocatorDump("web_cache/Script_resources"); |
| dump3->AddScalar("size", "bytes", |
| stats.scripts.encoded_size + stats.scripts.overhead_size); |
| WebMemoryAllocatorDump* dump4 = memory_dump->CreateMemoryAllocatorDump( |
| "web_cache/XSL stylesheet_resources"); |
| dump4->AddScalar("size", "bytes", |
| stats.xsl_style_sheets.encoded_size + |
| stats.xsl_style_sheets.overhead_size); |
| WebMemoryAllocatorDump* dump5 = |
| memory_dump->CreateMemoryAllocatorDump("web_cache/Font_resources"); |
| dump5->AddScalar("size", "bytes", |
| stats.fonts.encoded_size + stats.fonts.overhead_size); |
| WebMemoryAllocatorDump* dump6 = |
| memory_dump->CreateMemoryAllocatorDump("web_cache/Other_resources"); |
| dump6->AddScalar("size", "bytes", |
| stats.other.encoded_size + stats.other.overhead_size); |
| return true; |
| } |
| |
| for (const auto& resource_map_iter : resource_maps_) { |
| for (const auto& resource_iter : *resource_map_iter.value) { |
| Resource* resource = resource_iter.value->GetResource(); |
| resource->OnMemoryDump(level_of_detail, memory_dump); |
| } |
| } |
| return true; |
| } |
| |
| void MemoryCache::OnMemoryPressure(WebMemoryPressureLevel level) { |
| PruneAll(); |
| } |
| |
| } // namespace blink |