#include "content/browser/code_cache/generated_code_cache.h"
#include <iostream>
#include <string_view>
#include "base/compiler_specific.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/byte_conversions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "components/services/storage/public/cpp/big_io_buffer.h"
#include "content/common/features.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/common/content_client.h"
#include "content/public/common/url_constants.h"
#include "crypto/hash.h"
#include "net/base/completion_once_callback.h"
#include "net/base/features.h"
#include "net/base/network_isolation_key.h"
#include "net/base/url_util.h"
#include "net/http/http_cache.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/loader/code_cache_util.h"
#include "third_party/blink/public/common/scheme_registry.h"
#include "url/gurl.h"
using storage::BigIOBuffer;
namespace content {
namespace {
constexpr char kSeparator[] = " \n";
void CheckValidResource(const GURL& resource_url,
GeneratedCodeCache::CodeCacheType cache_type) {
DCHECK(resource_url.is_valid());
bool resource_url_is_chrome_or_chrome_untrusted =
resource_url.SchemeIs(content::kChromeUIScheme) ||
resource_url.SchemeIs(content::kChromeUIUntrustedScheme);
DCHECK(
resource_url.SchemeIsHTTPOrHTTPS() ||
resource_url_is_chrome_or_chrome_untrusted ||
blink::CommonSchemeRegistry::IsExtensionScheme(resource_url.GetScheme()));
if (!blink::features::IsPersistentCacheForCodeCacheEnabled()) {
DCHECK_EQ(resource_url_is_chrome_or_chrome_untrusted,
cache_type == GeneratedCodeCache::kWebUIJavaScript);
}
}
void CheckValidContext(const GURL& origin_lock,
GeneratedCodeCache::CodeCacheType cache_type) {
bool origin_lock_is_chrome_or_chrome_untrusted =
origin_lock.SchemeIs(content::kChromeUIScheme) ||
origin_lock.SchemeIs(content::kChromeUIUntrustedScheme);
DCHECK(origin_lock.is_empty() ||
((origin_lock.SchemeIsHTTPOrHTTPS() ||
origin_lock_is_chrome_or_chrome_untrusted ||
blink::CommonSchemeRegistry::IsExtensionScheme(
origin_lock.GetScheme())) &&
!url::Origin::Create(origin_lock).opaque()));
if (!blink::features::IsPersistentCacheForCodeCacheEnabled()) {
DCHECK_EQ(origin_lock_is_chrome_or_chrome_untrusted,
cache_type == GeneratedCodeCache::kWebUIJavaScript);
}
}
std::string GetCacheKey(const GURL& resource_url,
const GURL& origin_lock,
const net::NetworkIsolationKey& nik,
GeneratedCodeCache::CodeCacheType cache_type) {
return base::StrCat(
{GeneratedCodeCache::GetResourceKey(resource_url, cache_type),
kSeparator,
GeneratedCodeCache::GetContextKey(origin_lock, nik, cache_type)});
}
constexpr size_t kResponseTimeSizeInBytes = sizeof(int64_t);
constexpr size_t kDataSizeInBytes = sizeof(uint32_t);
constexpr size_t kHeaderSizeInBytes =
kResponseTimeSizeInBytes + kDataSizeInBytes;
constexpr size_t kSHAKeySizeInBytes = 2 * crypto::hash::kSha256Size;
constexpr size_t kInlineDataLimit = 4096;
constexpr size_t kDedicatedDataLimit = 16384;
void WriteCommonDataHeader(net::IOBufferWithSize* buffer,
const base::Time& response_time,
uint32_t data_size) {
auto header = buffer->span().first<kHeaderSizeInBytes>();
auto [header_time, header_size] = header.split_at<kResponseTimeSizeInBytes>();
header_time.copy_from(base::I64ToLittleEndian(
response_time.ToDeltaSinceWindowsEpoch().InMicroseconds()));
header_size.copy_from(base::U32ToLittleEndian(data_size));
}
void ReadCommonDataHeader(net::IOBufferWithSize* buffer,
base::Time* response_time,
uint32_t* data_size) {
auto header = buffer->span().first<kHeaderSizeInBytes>();
auto [header_time, header_size] = header.split_at<kResponseTimeSizeInBytes>();
int64_t raw_response_time = base::I64FromLittleEndian(header_time);
*response_time = base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(raw_response_time));
*data_size = base::U32FromLittleEndian(header_size);
}
static_assert(mojo_base::BigBuffer::kMaxInlineBytes <=
std::numeric_limits<int>::max(),
"Buffer size calculations may overflow int");
net::CacheType CodeCacheTypeToNetCacheType(
GeneratedCodeCache::CodeCacheType type) {
switch (type) {
case GeneratedCodeCache::CodeCacheType::kJavaScript:
return net::GENERATED_BYTE_CODE_CACHE;
case GeneratedCodeCache::CodeCacheType::kWebAssembly:
return net::GENERATED_NATIVE_CODE_CACHE;
case GeneratedCodeCache::CodeCacheType::kWebUIJavaScript:
return net::GENERATED_WEBUI_BYTE_CODE_CACHE;
}
NOTREACHED();
}
void CollectStatisticsForEmbedderWebUIPages(
const GURL& resource_url,
const GURL& origin_lock,
GeneratedCodeCache::CacheEntryStatus entry_status) {
const content::ContentBrowserClient* browser_client =
GetContentClient()->browser();
CHECK(browser_client);
const std::string resource_hostname =
browser_client->GetWebUIHostnameForCodeCacheMetrics(resource_url);
const std::string origin_hostname =
browser_client->GetWebUIHostnameForCodeCacheMetrics(origin_lock);
if (!resource_hostname.empty()) {
base::UmaHistogramEnumeration(
base::StrCat({"SiteIsolatedCodeCache.JS.WebUI.",
std::move(resource_hostname), ".Resource.Behaviour"}),
entry_status);
}
if (!origin_hostname.empty()) {
base::UmaHistogramEnumeration(
base::StrCat({"SiteIsolatedCodeCache.JS.WebUI.",
std::move(origin_hostname), ".Origin.Behaviour"}),
entry_status);
}
}
}
std::string GeneratedCodeCache::GetResourceKey(
const GURL& resource_url,
GeneratedCodeCache::CodeCacheType cache_type) {
CheckValidResource(resource_url, cache_type);
return blink::UrlToCodeCacheKey(resource_url);
}
std::string GeneratedCodeCache::GetContextKey(
const GURL& origin_lock,
const net::NetworkIsolationKey& nik,
GeneratedCodeCache::CodeCacheType cache_type) {
CheckValidContext(origin_lock, cache_type);
std::string key;
if (origin_lock.is_valid()) {
key.append(net::SimplifyUrlForRequest(origin_lock).spec());
}
if (net::HttpCache::IsSplitCacheEnabled() &&
base::FeatureList::IsEnabled(
net::features::kSplitCodeCacheByNetworkIsolationKey)) {
if (!nik.IsTransient()) {
key.append(kSeparator);
key.append(*nik.ToCacheKeyString());
}
}
return key;
}
bool GeneratedCodeCache::IsValidHeader(
scoped_refptr<net::IOBufferWithSize> small_buffer) const {
size_t buffer_size = small_buffer->size();
if (buffer_size < kHeaderSizeInBytes) {
return false;
}
base::Time response_time;
uint32_t data_size = 0;
ReadCommonDataHeader(small_buffer.get(), &response_time, &data_size);
if (data_size <= kInlineDataLimit) {
return buffer_size == kHeaderSizeInBytes + data_size;
}
if (!ShouldDeduplicateEntry(data_size)) {
return buffer_size == kHeaderSizeInBytes;
}
return buffer_size == kHeaderSizeInBytes + kSHAKeySizeInBytes;
}
std::string GeneratedCodeCache::GetResourceURLFromKey(const std::string& key) {
constexpr size_t kPrefixStringLen = std::size(blink::kCodeCacheKeyPrefix) - 1;
const size_t separator_index = key.find(kSeparator);
if (key.length() < kPrefixStringLen || separator_index == std::string::npos) {
return std::string();
}
std::string resource_url =
key.substr(kPrefixStringLen, separator_index - kPrefixStringLen);
return resource_url;
}
void GeneratedCodeCache::CollectStatistics(
const GURL& resource_url,
const GURL& origin_lock,
GeneratedCodeCache::CacheEntryStatus status) {
switch (cache_type_) {
case GeneratedCodeCache::CodeCacheType::kJavaScript:
UMA_HISTOGRAM_ENUMERATION("SiteIsolatedCodeCache.JS.Behaviour", status);
break;
case GeneratedCodeCache::CodeCacheType::kWebUIJavaScript:
UMA_HISTOGRAM_ENUMERATION("SiteIsolatedCodeCache.JS.Behaviour", status);
UMA_HISTOGRAM_ENUMERATION("SiteIsolatedCodeCache.JS.WebUI.Behaviour",
status);
CollectStatisticsForEmbedderWebUIPages(resource_url, origin_lock, status);
break;
case GeneratedCodeCache::CodeCacheType::kWebAssembly:
UMA_HISTOGRAM_ENUMERATION("SiteIsolatedCodeCache.WASM.Behaviour", status);
break;
}
}
class GeneratedCodeCache::PendingOperation {
public:
PendingOperation(Operation op,
const GURL& resource_url,
const GURL& origin_lock,
const std::string& key,
scoped_refptr<net::IOBufferWithSize> small_buffer,
scoped_refptr<BigIOBuffer> large_buffer)
: op_(op),
resource_url_(resource_url),
origin_lock_(origin_lock),
key_(key),
small_buffer_(small_buffer),
large_buffer_(large_buffer) {
DCHECK(Operation::kWrite == op_ || Operation::kWriteWithSHAKey == op_);
}
PendingOperation(Operation op,
const GURL& resource_url,
const GURL& origin_lock,
const std::string& key,
ReadDataCallback read_callback)
: op_(op),
resource_url_(resource_url),
origin_lock_(origin_lock),
key_(key),
read_callback_(std::move(read_callback)) {
DCHECK_EQ(Operation::kFetch, op_);
}
PendingOperation(Operation op,
const GURL& resource_url,
const GURL& origin_lock,
const std::string& key,
const base::Time& response_time,
const base::TimeTicks start_time,
scoped_refptr<net::IOBufferWithSize> small_buffer,
scoped_refptr<BigIOBuffer> large_buffer,
ReadDataCallback read_callback)
: op_(op),
resource_url_(resource_url),
origin_lock_(origin_lock),
key_(key),
response_time_(response_time),
start_time_(start_time),
small_buffer_(small_buffer),
large_buffer_(large_buffer),
read_callback_(std::move(read_callback)) {
DCHECK_EQ(Operation::kFetchWithSHAKey, op_);
}
PendingOperation(Operation op,
const GURL& resource_url,
const GURL& origin_lock,
const std::string& key)
: op_(op),
resource_url_(resource_url),
origin_lock_(origin_lock),
key_(key) {
DCHECK_EQ(Operation::kDelete, op_);
}
PendingOperation(Operation op, GetBackendCallback backend_callback)
: op_(op), backend_callback_(std::move(backend_callback)) {
DCHECK_EQ(Operation::kGetBackend, op_);
}
~PendingOperation();
Operation operation() const { return op_; }
const std::string& key() const { return key_; }
const GURL& resource_url() const { return resource_url_; }
const GURL& origin_lock() const { return origin_lock_; }
scoped_refptr<net::IOBufferWithSize> small_buffer() { return small_buffer_; }
scoped_refptr<BigIOBuffer> large_buffer() { return large_buffer_; }
ReadDataCallback TakeReadCallback() { return std::move(read_callback_); }
void RunReadCallback(GeneratedCodeCache* code_cache,
base::Time response_time,
mojo_base::BigBuffer data) {
if (code_cache->cache_type_ == CodeCacheType::kJavaScript) {
const bool code_cache_hit = data.size() > 0;
const bool in_memory_code_cache_hit = code_cache->lru_cache_.Has(key_);
if (code_cache_hit && !in_memory_code_cache_hit) {
code_cache->lru_cache_.Put(key_, response_time, base::span(data));
}
if (!base::FeatureList::IsEnabled(features::kInMemoryCodeCache)) {
if (code_cache_hit && in_memory_code_cache_hit) {
base::UmaHistogramTimes(
"SiteIsolatedCodeCache.JS.MemoryBackedCodeCachePotentialImpact",
base::TimeTicks::Now() - start_time_);
}
base::UmaHistogramBoolean("SiteIsolatedCodeCache.JS.Hit",
code_cache_hit);
base::UmaHistogramBoolean(
"SiteIsolatedCodeCache.JS.PotentialMemoryBackedCodeCacheHit",
in_memory_code_cache_hit);
}
}
std::move(read_callback_).Run(response_time, std::move(data));
}
GetBackendCallback TakeBackendCallback() {
return std::move(backend_callback_);
}
void set_small_buffer(scoped_refptr<net::IOBufferWithSize> small_buffer) {
DCHECK_EQ(Operation::kFetch, op_);
small_buffer_ = small_buffer;
}
void set_large_buffer(scoped_refptr<BigIOBuffer> large_buffer) {
DCHECK_EQ(Operation::kFetch, op_);
large_buffer_ = large_buffer;
}
const base::Time& response_time() const {
DCHECK_EQ(Operation::kFetchWithSHAKey, op_);
return response_time_;
}
base::TimeTicks start_time() const { return start_time_; }
bool succeeded() const { return succeeded_; }
bool AddBufferCompletion(bool succeeded) {
DCHECK(op_ == Operation::kWrite || op_ == Operation::kWriteWithSHAKey ||
op_ == Operation::kFetch || op_ == Operation::kFetchWithSHAKey);
if (!succeeded)
succeeded_ = false;
DCHECK_GT(2, completions_);
completions_++;
return completions_ == 2;
}
private:
const Operation op_;
const GURL resource_url_;
const GURL origin_lock_;
const std::string key_;
const base::Time response_time_;
const base::TimeTicks start_time_ = base::TimeTicks::Now();
scoped_refptr<net::IOBufferWithSize> small_buffer_;
scoped_refptr<BigIOBuffer> large_buffer_;
ReadDataCallback read_callback_;
GetBackendCallback backend_callback_;
int completions_ = 0;
bool succeeded_ = true;
};
GeneratedCodeCache::PendingOperation::~PendingOperation() = default;
GeneratedCodeCache::GeneratedCodeCache(const base::FilePath& path,
int max_size_bytes,
CodeCacheType cache_type)
: backend_state_(kInitializing),
path_(path),
max_size_bytes_(max_size_bytes),
cache_type_(cache_type),
lru_cache_(max_size_bytes == 0
? kLruCacheCapacity
: std::min<int64_t>(kLruCacheCapacity, max_size_bytes)) {
CreateBackend();
}
GeneratedCodeCache::~GeneratedCodeCache() = default;
void GeneratedCodeCache::GetBackend(GetBackendCallback callback) {
switch (backend_state_) {
case kFailed:
std::move(callback).Run(nullptr);
return;
case kInitialized:
std::move(callback).Run(backend_.get());
return;
case kInitializing:
pending_ops_.emplace(std::make_unique<PendingOperation>(
Operation::kGetBackend, std::move(callback)));
return;
}
}
void GeneratedCodeCache::WriteEntry(const GURL& url,
const GURL& origin_lock,
const net::NetworkIsolationKey& nik,
const base::Time& response_time,
mojo_base::BigBuffer data) {
if (backend_state_ == kFailed) {
CollectStatistics(url, origin_lock, CacheEntryStatus::kError);
return;
}
if (data.size() >= std::numeric_limits<int32_t>::max())
return;
const std::string key = GetCacheKey(url, origin_lock, nik, cache_type_);
if (cache_type_ == CodeCacheType::kJavaScript) {
lru_cache_.Put(key, response_time, base::span(data));
}
scoped_refptr<net::IOBufferWithSize> small_buffer;
scoped_refptr<BigIOBuffer> large_buffer;
const uint32_t data_size = static_cast<uint32_t>(data.size());
if (data_size <= kInlineDataLimit) {
small_buffer = base::MakeRefCounted<net::IOBufferWithSize>(
kHeaderSizeInBytes + data.size());
small_buffer->span().subspan(kHeaderSizeInBytes).copy_from(data);
large_buffer = base::MakeRefCounted<BigIOBuffer>(mojo_base::BigBuffer());
} else if (!ShouldDeduplicateEntry(data_size)) {
small_buffer =
base::MakeRefCounted<net::IOBufferWithSize>(kHeaderSizeInBytes);
large_buffer = base::MakeRefCounted<BigIOBuffer>(std::move(data));
} else {
mojo_base::BigBuffer copy(base::span{data});
if (copy.size() != data.size())
return;
data = mojo_base::BigBuffer();
std::string checksum_key = base::HexEncode(crypto::hash::Sha256(copy));
DCHECK_EQ(kSHAKeySizeInBytes, checksum_key.length());
small_buffer = base::MakeRefCounted<net::IOBufferWithSize>(
kHeaderSizeInBytes + checksum_key.length());
small_buffer->span()
.subspan(kHeaderSizeInBytes)
.copy_from(base::as_byte_span(checksum_key));
large_buffer = base::MakeRefCounted<BigIOBuffer>(mojo_base::BigBuffer());
auto small_buffer2 = base::MakeRefCounted<net::IOBufferWithSize>(0);
auto large_buffer2 = base::MakeRefCounted<BigIOBuffer>(std::move(copy));
auto op2 = std::make_unique<PendingOperation>(
Operation::kWriteWithSHAKey, url, origin_lock, checksum_key,
small_buffer2, large_buffer2);
EnqueueOperation(std::move(op2));
}
WriteCommonDataHeader(small_buffer.get(), response_time, data_size);
auto op = std::make_unique<PendingOperation>(
Operation::kWrite, url, origin_lock, key, small_buffer, large_buffer);
EnqueueOperation(std::move(op));
}
void GeneratedCodeCache::FetchEntry(const GURL& url,
const GURL& origin_lock,
const net::NetworkIsolationKey& nik,
ReadDataCallback read_data_callback) {
if (backend_state_ == kFailed) {
CollectStatistics(url, origin_lock, CacheEntryStatus::kError);
std::move(read_data_callback).Run(base::Time(), mojo_base::BigBuffer());
return;
}
std::string key = GetCacheKey(url, origin_lock, nik, cache_type_);
auto op = std::make_unique<PendingOperation>(
Operation::kFetch, url, origin_lock, key, std::move(read_data_callback));
EnqueueOperation(std::move(op));
}
void GeneratedCodeCache::DeleteEntry(const GURL& url,
const GURL& origin_lock,
const net::NetworkIsolationKey& nik) {
if (backend_state_ == kFailed) {
CollectStatistics(url, origin_lock, CacheEntryStatus::kError);
return;
}
std::string key = GetCacheKey(url, origin_lock, nik, cache_type_);
auto op = std::make_unique<PendingOperation>(Operation::kDelete, url,
origin_lock, key);
EnqueueOperation(std::move(op));
lru_cache_.Delete(key);
}
void GeneratedCodeCache::CreateBackend() {
disk_cache::BackendResult result = disk_cache::CreateCacheBackend(
CodeCacheTypeToNetCacheType(cache_type_), net::CACHE_BACKEND_SIMPLE,
nullptr, path_, max_size_bytes_,
disk_cache::ResetHandling::kResetOnError, nullptr,
base::BindOnce(&GeneratedCodeCache::DidCreateBackend,
weak_ptr_factory_.GetWeakPtr()));
if (result.net_error != net::ERR_IO_PENDING) {
DidCreateBackend(std::move(result));
}
}
void GeneratedCodeCache::DidCreateBackend(disk_cache::BackendResult result) {
if (result.net_error != net::OK) {
backend_state_ = kFailed;
} else {
backend_ = std::move(result.backend);
backend_state_ = kInitialized;
}
IssuePendingOperations();
}
void GeneratedCodeCache::EnqueueOperation(
std::unique_ptr<PendingOperation> op) {
if (backend_state_ != kInitialized) {
pending_ops_.emplace(std::move(op));
return;
}
EnqueueOperationAndIssueIfNext(std::move(op));
}
void GeneratedCodeCache::IssuePendingOperations() {
while (!pending_ops_.empty()) {
std::unique_ptr<PendingOperation> op = std::move(pending_ops_.front());
pending_ops_.pop();
if (op->operation() != Operation::kGetBackend) {
EnqueueOperationAndIssueIfNext(std::move(op));
} else {
IssueOperation(op.get());
}
}
}
void GeneratedCodeCache::IssueOperation(PendingOperation* op) {
switch (op->operation()) {
case kFetch:
case kFetchWithSHAKey:
FetchEntryImpl(op);
break;
case kWrite:
case kWriteWithSHAKey:
WriteEntryImpl(op);
break;
case kDelete:
DeleteEntryImpl(op);
break;
case kGetBackend:
DoPendingGetBackend(op);
break;
}
}
void GeneratedCodeCache::WriteEntryImpl(PendingOperation* op) {
DCHECK(Operation::kWrite == op->operation() ||
Operation::kWriteWithSHAKey == op->operation());
if (backend_state_ != kInitialized) {
CloseOperationAndIssueNext(op);
return;
}
disk_cache::EntryResult result = backend_->OpenOrCreateEntry(
op->key(), net::LOW,
base::BindOnce(&GeneratedCodeCache::OpenCompleteForWrite,
weak_ptr_factory_.GetWeakPtr(), op));
if (result.net_error() != net::ERR_IO_PENDING) {
OpenCompleteForWrite(op, std::move(result));
}
}
void GeneratedCodeCache::OpenCompleteForWrite(
PendingOperation* op,
disk_cache::EntryResult entry_result) {
DCHECK(Operation::kWrite == op->operation() ||
Operation::kWriteWithSHAKey == op->operation());
if (entry_result.net_error() != net::OK) {
CollectStatistics(op->resource_url(), op->origin_lock(),
CacheEntryStatus::kError);
CloseOperationAndIssueNext(op);
return;
}
if (entry_result.opened()) {
CollectStatistics(op->resource_url(), op->origin_lock(),
CacheEntryStatus::kUpdate);
} else {
CollectStatistics(op->resource_url(), op->origin_lock(),
CacheEntryStatus::kCreate);
}
disk_cache::ScopedEntryPtr entry(entry_result.ReleaseEntry());
DCHECK(entry);
if (op->operation() == Operation::kWriteWithSHAKey) {
int small_size = entry->GetDataSize(kSmallDataStream);
int large_size = entry->GetDataSize(kLargeDataStream);
if (small_size == 0 && large_size == op->large_buffer()->size()) {
CloseOperationAndIssueNext(op);
return;
}
DCHECK_EQ(0, small_size);
DCHECK_EQ(0, large_size);
}
auto small_buffer = op->small_buffer();
int result = entry->WriteData(
kSmallDataStream, 0, small_buffer.get(), small_buffer->size(),
base::BindOnce(&GeneratedCodeCache::WriteSmallBufferComplete,
weak_ptr_factory_.GetWeakPtr(), op),
true);
if (result != net::ERR_IO_PENDING) {
WriteSmallBufferComplete(op, result);
}
auto large_buffer = op->large_buffer();
result = entry->WriteData(
kLargeDataStream, 0, large_buffer.get(), large_buffer->size(),
base::BindOnce(&GeneratedCodeCache::WriteLargeBufferComplete,
weak_ptr_factory_.GetWeakPtr(), op),
true);
if (result != net::ERR_IO_PENDING) {
WriteLargeBufferComplete(op, result);
}
}
void GeneratedCodeCache::WriteSmallBufferComplete(PendingOperation* op,
int rv) {
DCHECK(Operation::kWrite == op->operation() ||
Operation::kWriteWithSHAKey == op->operation());
if (op->AddBufferCompletion(rv == op->small_buffer()->size())) {
WriteComplete(op);
}
}
void GeneratedCodeCache::WriteLargeBufferComplete(PendingOperation* op,
int rv) {
DCHECK(Operation::kWrite == op->operation() ||
Operation::kWriteWithSHAKey == op->operation());
if (op->AddBufferCompletion(rv == op->large_buffer()->size())) {
WriteComplete(op);
}
}
void GeneratedCodeCache::WriteComplete(PendingOperation* op) {
DCHECK(Operation::kWrite == op->operation() ||
Operation::kWriteWithSHAKey == op->operation());
if (!op->succeeded()) {
CollectStatistics(op->resource_url(), op->origin_lock(),
CacheEntryStatus::kWriteFailed);
DoomEntry(op);
}
CloseOperationAndIssueNext(op);
}
void GeneratedCodeCache::FetchEntryImpl(PendingOperation* op) {
DCHECK(Operation::kFetch == op->operation() ||
Operation::kFetchWithSHAKey == op->operation());
if (base::FeatureList::IsEnabled(features::kInMemoryCodeCache)) {
if (auto result = lru_cache_.Get(op->key())) {
op->RunReadCallback(this, result->response_time, std::move(result->data));
CloseOperationAndIssueNext(op);
return;
}
}
if (backend_state_ != kInitialized) {
op->RunReadCallback(this, base::Time(), mojo_base::BigBuffer());
CloseOperationAndIssueNext(op);
return;
}
disk_cache::EntryResult result = backend_->OpenEntry(
op->key(), net::HIGHEST,
base::BindOnce(&GeneratedCodeCache::OpenCompleteForRead,
weak_ptr_factory_.GetWeakPtr(), op));
if (result.net_error() != net::ERR_IO_PENDING) {
OpenCompleteForRead(op, std::move(result));
}
}
void GeneratedCodeCache::OpenCompleteForRead(
PendingOperation* op,
disk_cache::EntryResult entry_result) {
DCHECK(Operation::kFetch == op->operation() ||
Operation::kFetchWithSHAKey == op->operation());
if (entry_result.net_error() != net::OK) {
CollectStatistics(op->resource_url(), op->origin_lock(),
CacheEntryStatus::kMiss);
op->RunReadCallback(this, base::Time(), mojo_base::BigBuffer());
CloseOperationAndIssueNext(op);
return;
}
disk_cache::ScopedEntryPtr entry(entry_result.ReleaseEntry());
DCHECK(entry);
int small_size = entry->GetDataSize(kSmallDataStream);
int large_size = entry->GetDataSize(kLargeDataStream);
scoped_refptr<net::IOBufferWithSize> small_buffer;
scoped_refptr<BigIOBuffer> large_buffer;
if (op->operation() == Operation::kFetch) {
small_buffer = base::MakeRefCounted<net::IOBufferWithSize>(small_size);
op->set_small_buffer(small_buffer);
large_buffer = base::MakeRefCounted<BigIOBuffer>(large_size);
op->set_large_buffer(large_buffer);
} else {
small_buffer = op->small_buffer();
large_buffer = op->large_buffer();
DCHECK_EQ(small_size, small_buffer->size());
DCHECK_EQ(large_size, large_buffer->size());
}
int result = entry->ReadData(
kSmallDataStream, 0, small_buffer.get(), small_buffer->size(),
base::BindOnce(&GeneratedCodeCache::ReadSmallBufferComplete,
weak_ptr_factory_.GetWeakPtr(), op));
if (result != net::ERR_IO_PENDING) {
ReadSmallBufferComplete(op, result);
}
if (large_size == 0)
return;
result = entry->ReadData(
kLargeDataStream, 0, large_buffer.get(), large_buffer->size(),
base::BindOnce(&GeneratedCodeCache::ReadLargeBufferComplete,
weak_ptr_factory_.GetWeakPtr(), op));
if (result != net::ERR_IO_PENDING) {
ReadLargeBufferComplete(op, result);
}
}
void GeneratedCodeCache::ReadSmallBufferComplete(PendingOperation* op, int rv) {
DCHECK(Operation::kFetch == op->operation() ||
Operation::kFetchWithSHAKey == op->operation());
bool no_header = op->operation() == Operation::kFetchWithSHAKey;
bool succeeded = (rv == op->small_buffer()->size() &&
(no_header || IsValidHeader(op->small_buffer())));
CollectStatistics(
op->resource_url(), op->origin_lock(),
succeeded ? CacheEntryStatus::kHit : CacheEntryStatus::kMiss);
if (op->AddBufferCompletion(succeeded))
ReadComplete(op);
if (op->large_buffer()->size() == 0)
ReadLargeBufferComplete(op, 0);
}
void GeneratedCodeCache::ReadLargeBufferComplete(PendingOperation* op, int rv) {
DCHECK(Operation::kFetch == op->operation() ||
Operation::kFetchWithSHAKey == op->operation());
if (op->AddBufferCompletion(rv == op->large_buffer()->size()))
ReadComplete(op);
}
void GeneratedCodeCache::ReadComplete(PendingOperation* op) {
DCHECK(Operation::kFetch == op->operation() ||
Operation::kFetchWithSHAKey == op->operation());
if (!op->succeeded()) {
op->RunReadCallback(this, base::Time(), mojo_base::BigBuffer());
DoomEntry(op);
} else {
if (op->operation() != Operation::kFetchWithSHAKey) {
base::Time response_time;
uint32_t data_size = 0;
ReadCommonDataHeader(op->small_buffer().get(), &response_time,
&data_size);
if (data_size <= kInlineDataLimit) {
DCHECK_EQ(0, op->large_buffer()->size());
mojo_base::BigBuffer data(
op->small_buffer()->span().subspan(kHeaderSizeInBytes, data_size));
op->RunReadCallback(this, response_time, std::move(data));
} else if (!ShouldDeduplicateEntry(data_size)) {
op->RunReadCallback(this, response_time,
op->large_buffer()->TakeBuffer());
} else {
DCHECK_EQ(static_cast<int>(kHeaderSizeInBytes + kSHAKeySizeInBytes),
op->small_buffer()->size());
std::string checksum_key(
UNSAFE_TODO(op->small_buffer()->data() + kHeaderSizeInBytes),
kSHAKeySizeInBytes);
auto small_buffer = base::MakeRefCounted<net::IOBufferWithSize>(0);
auto large_buffer = base::MakeRefCounted<BigIOBuffer>(data_size);
auto op2 = std::make_unique<PendingOperation>(
Operation::kFetchWithSHAKey, op->resource_url(), op->origin_lock(),
checksum_key, response_time, op->start_time(), small_buffer,
large_buffer, op->TakeReadCallback());
EnqueueOperation(std::move(op2));
}
} else {
op->RunReadCallback(this, op->response_time(),
op->large_buffer()->TakeBuffer());
}
}
CloseOperationAndIssueNext(op);
}
void GeneratedCodeCache::DeleteEntryImpl(PendingOperation* op) {
DCHECK_EQ(Operation::kDelete, op->operation());
DoomEntry(op);
CloseOperationAndIssueNext(op);
}
void GeneratedCodeCache::DoomEntry(PendingOperation* op) {
DCHECK_NE(Operation::kGetBackend, op->operation());
DCHECK_EQ(kInitialized, backend_state_);
CollectStatistics(op->resource_url(), op->origin_lock(),
CacheEntryStatus::kClear);
backend_->DoomEntry(op->key(), net::LOWEST, net::CompletionOnceCallback());
}
void GeneratedCodeCache::IssueNextOperation(const std::string& key) {
auto it = active_entries_map_.find(key);
if (it == active_entries_map_.end())
return;
DCHECK(!it->second.empty());
IssueOperation(it->second.front().get());
}
void GeneratedCodeCache::CloseOperationAndIssueNext(PendingOperation* op) {
std::unique_ptr<PendingOperation> keep_alive = DequeueOperation(op);
IssueNextOperation(op->key());
}
void GeneratedCodeCache::EnqueueOperationAndIssueIfNext(
std::unique_ptr<PendingOperation> op) {
DCHECK_NE(Operation::kGetBackend, op->operation());
auto it = active_entries_map_.find(op->key());
bool can_issue = false;
if (it == active_entries_map_.end()) {
it = active_entries_map_.emplace(op->key(), PendingOperationQueue()).first;
can_issue = true;
}
const std::string& key = op->key();
it->second.emplace(std::move(op));
if (can_issue)
IssueNextOperation(key);
}
std::unique_ptr<GeneratedCodeCache::PendingOperation>
GeneratedCodeCache::DequeueOperation(PendingOperation* op) {
auto it = active_entries_map_.find(op->key());
CHECK(it != active_entries_map_.end());
DCHECK(!it->second.empty());
std::unique_ptr<PendingOperation> result = std::move(it->second.front());
DCHECK_EQ(op, result.get());
it->second.pop();
if (it->second.empty()) {
active_entries_map_.erase(it);
}
return result;
}
void GeneratedCodeCache::DoPendingGetBackend(PendingOperation* op) {
DCHECK_EQ(kGetBackend, op->operation());
if (backend_state_ == kInitialized) {
op->TakeBackendCallback().Run(backend_.get());
} else {
DCHECK_EQ(backend_state_, kFailed);
op->TakeBackendCallback().Run(nullptr);
}
}
bool GeneratedCodeCache::IsDeduplicationEnabled() const {
return cache_type_ != kWebUIJavaScript;
}
bool GeneratedCodeCache::ShouldDeduplicateEntry(uint32_t data_size) const {
return data_size > kDedicatedDataLimit && IsDeduplicationEnabled();
}
void GeneratedCodeCache::SetLastUsedTimeForTest(
const GURL& resource_url,
const GURL& origin_lock,
const net::NetworkIsolationKey& nik,
base::Time time,
base::OnceClosure user_callback) {
DCHECK_EQ(backend_state_, kInitialized);
auto split = base::SplitOnceCallback(std::move(user_callback));
disk_cache::EntryResultCallback callback = base::BindOnce(
&GeneratedCodeCache::OpenCompleteForSetLastUsedForTest,
weak_ptr_factory_.GetWeakPtr(), time, std::move(split.first));
std::string key = GetCacheKey(resource_url, origin_lock, nik, cache_type_);
disk_cache::EntryResult result =
backend_->OpenEntry(key, net::LOWEST, std::move(callback));
if (result.net_error() != net::ERR_IO_PENDING) {
OpenCompleteForSetLastUsedForTest(time, std::move(split.second),
std::move(result));
}
}
void GeneratedCodeCache::ClearInMemoryCache() {
lru_cache_.Clear();
}
void GeneratedCodeCache::OpenCompleteForSetLastUsedForTest(
base::Time time,
base::OnceClosure callback,
disk_cache::EntryResult result) {
DCHECK_EQ(result.net_error(), net::OK);
{
disk_cache::ScopedEntryPtr disk_entry(result.ReleaseEntry());
DCHECK(disk_entry);
disk_entry->SetLastUsedTimeForTest(time);
}
std::move(callback).Run();
}
void GeneratedCodeCache::CollectStatisticsForTest(
const GURL& resource_url,
const GURL& origin_lock,
GeneratedCodeCache::CacheEntryStatus status) {
CollectStatistics(resource_url, origin_lock, status);
}
}