|  | // Copyright 2018 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "content/browser/code_cache/generated_code_cache.h" | 
|  | #include "base/bind.h" | 
|  | #include "base/feature_list.h" | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "base/metrics/field_trial_params.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "content/public/common/url_constants.h" | 
|  | #include "crypto/sha2.h" | 
|  | #include "net/base/completion_once_callback.h" | 
|  | #include "net/base/url_util.h" | 
|  | #include "url/gurl.h" | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | constexpr char kPrefix[] = "_key"; | 
|  | constexpr char kSeparator[] = " \n"; | 
|  |  | 
|  | // We always expect to receive valid URLs that can be used as keys to the code | 
|  | // cache. The relevant checks (for ex: resource_url is valid, origin_lock is | 
|  | // not opque etc.,) must be done prior to requesting the code cache. | 
|  | // | 
|  | // This function doesn't enforce anything in the production code. It is here | 
|  | // to make the assumptions explicit and to catch any errors when DCHECKs are | 
|  | // enabled. | 
|  | void CheckValidKeys(const GURL& resource_url, const GURL& origin_lock) { | 
|  | // If the resource url is invalid don't cache the code. | 
|  | DCHECK(resource_url.is_valid() && resource_url.SchemeIsHTTPOrHTTPS()); | 
|  |  | 
|  | // |origin_lock| should be either empty or should have Http/Https/chrome | 
|  | // schemes and it should not be a URL with opaque origin. Empty origin_locks | 
|  | // are allowed when the renderer is not locked to an origin. | 
|  | DCHECK(origin_lock.is_empty() || | 
|  | ((origin_lock.SchemeIsHTTPOrHTTPS() || | 
|  | origin_lock.SchemeIs(content::kChromeUIScheme)) && | 
|  | !url::Origin::Create(origin_lock).opaque())); | 
|  | } | 
|  |  | 
|  | // Generates the cache key for the given |resource_url| and the |origin_lock|. | 
|  | //   |resource_url| is the url corresponding to the requested resource. | 
|  | //   |origin_lock| is the origin that the renderer which requested this | 
|  | //   resource is locked to. | 
|  | // For example, if SitePerProcess is enabled and http://script.com/script1.js is | 
|  | // requested by http://example.com, then http://script.com/script.js is the | 
|  | // resource_url and http://example.com is the origin_lock. | 
|  | // | 
|  | // This returns the key by concatenating the serialized url and origin lock | 
|  | // with a separator in between. |origin_lock| could be empty when renderer is | 
|  | // not locked to an origin (ex: SitePerProcess is disabled) and it is safe to | 
|  | // use only |resource_url| as the key in such cases. | 
|  | // TODO(wjmaclean): Either convert this to use a SiteInfo object, or convert it | 
|  | // to something not based on URLs. | 
|  | std::string GetCacheKey(const GURL& resource_url, const GURL& origin_lock) { | 
|  | CheckValidKeys(resource_url, origin_lock); | 
|  |  | 
|  | // Add a prefix _ so it can't be parsed as a valid URL. | 
|  | std::string key(kPrefix); | 
|  | // Remove reference, username and password sections of the URL. | 
|  | key.append(net::SimplifyUrlForRequest(resource_url).spec()); | 
|  | // Add a separator between URL and origin to avoid any possibility of | 
|  | // attacks by crafting the URL. URLs do not contain any control ASCII | 
|  | // characters, and also space is encoded. So use ' \n' as a seperator. | 
|  | key.append(kSeparator); | 
|  |  | 
|  | if (origin_lock.is_valid()) | 
|  | key.append(net::SimplifyUrlForRequest(origin_lock).spec()); | 
|  | return key; | 
|  | } | 
|  |  | 
|  | constexpr size_t kResponseTimeSizeInBytes = sizeof(int64_t); | 
|  | constexpr size_t kDataSizeInBytes = sizeof(uint32_t); | 
|  | constexpr size_t kHeaderSizeInBytes = | 
|  | kResponseTimeSizeInBytes + kDataSizeInBytes; | 
|  | // The SHA-256 checksum is used as the key for the de-duplicated code data. We | 
|  | // must convert the checksum to a string key in a way that is guaranteed not to | 
|  | // match a key generated by |GetCacheKey|. A simple way to do this is to convert | 
|  | // it to a hex number string, which is twice as long as the checksum. | 
|  | constexpr size_t kSHAKeySizeInBytes = 2 * crypto::kSHA256Length; | 
|  |  | 
|  | // This is the threshold for storing the header and cached code in stream 0, | 
|  | // which is read into memory on opening an entry. JavaScript code caching stores | 
|  | // time stamps with no data, or timestamps with just a tag, and we observe many | 
|  | // 8 and 16 byte reads and writes. Make the threshold larger to speed up small | 
|  | // code entries too. | 
|  | constexpr size_t kSmallDataLimit = 4096; | 
|  | // This is the maximum size for code that will be stored under the key generated | 
|  | // by |GetCacheKey|. Each origin will get its own copy of the generated code for | 
|  | // a given resource. Code that is larger than this limit will be stored under a | 
|  | // key derived from the code checksum, and each origin using a given resource | 
|  | // gets its own small entry under the key generated by |GetCacheKey| that holds | 
|  | // the hash, enabling a two stage lookup. | 
|  | constexpr size_t kLargeDataLimit = 64 * 1024; | 
|  |  | 
|  | // crbug.com/936107: This is a study to determine a good size threshold to | 
|  | // deduplicate JS and WebAssembly cache entries. | 
|  | // TODO(bbudge): Remove this after the study finishes. | 
|  | constexpr base::Feature kCodeCacheDeduplicationStudy{ | 
|  | "CodeCacheDeduplicationStudy", base::FEATURE_DISABLED_BY_DEFAULT}; | 
|  | constexpr base::FeatureParam<int> kCodeCacheDeduplicationThreshold{ | 
|  | &kCodeCacheDeduplicationStudy, "size", kLargeDataLimit}; | 
|  |  | 
|  | size_t GetLargeDataLimit() { | 
|  | // The large data limit should be greater than or equal to kSmallDataLimit. | 
|  | return std::max(kSmallDataLimit, base::saturated_cast<size_t>( | 
|  | kCodeCacheDeduplicationThreshold.Get())); | 
|  | } | 
|  |  | 
|  | // Checks that the header data in the small buffer is valid. We may read cache | 
|  | // entries that were written by a previous version of Chrome which use obsolete | 
|  | // formats. These reads should fail and be doomed as soon as possible. | 
|  | bool IsValidHeader(scoped_refptr<net::IOBufferWithSize> small_buffer) { | 
|  | size_t buffer_size = small_buffer->size(); | 
|  | if (buffer_size < kHeaderSizeInBytes) | 
|  | return false; | 
|  | uint32_t data_size; | 
|  | memcpy(&data_size, small_buffer->data() + kResponseTimeSizeInBytes, | 
|  | kDataSizeInBytes); | 
|  | if (data_size <= kSmallDataLimit) | 
|  | return buffer_size == kHeaderSizeInBytes + data_size; | 
|  | if (data_size <= GetLargeDataLimit()) | 
|  | return buffer_size == kHeaderSizeInBytes; | 
|  | return buffer_size == kHeaderSizeInBytes + kSHAKeySizeInBytes; | 
|  | } | 
|  |  | 
|  | void WriteCommonDataHeader(scoped_refptr<net::IOBufferWithSize> buffer, | 
|  | const base::Time& response_time, | 
|  | uint32_t data_size) { | 
|  | DCHECK_LE(static_cast<int>(kHeaderSizeInBytes), buffer->size()); | 
|  | int64_t serialized_time = | 
|  | response_time.ToDeltaSinceWindowsEpoch().InMicroseconds(); | 
|  | memcpy(buffer->data(), &serialized_time, kResponseTimeSizeInBytes); | 
|  | // Copy size to small data buffer. | 
|  | memcpy(buffer->data() + kResponseTimeSizeInBytes, &data_size, | 
|  | kDataSizeInBytes); | 
|  | } | 
|  |  | 
|  | void ReadCommonDataHeader(scoped_refptr<net::IOBufferWithSize> buffer, | 
|  | base::Time* response_time, | 
|  | uint32_t* data_size) { | 
|  | DCHECK_LE(static_cast<int>(kHeaderSizeInBytes), buffer->size()); | 
|  | int64_t raw_response_time; | 
|  | memcpy(&raw_response_time, buffer->data(), kResponseTimeSizeInBytes); | 
|  | *response_time = base::Time::FromDeltaSinceWindowsEpoch( | 
|  | base::TimeDelta::FromMicroseconds(raw_response_time)); | 
|  | memcpy(data_size, buffer->data() + kResponseTimeSizeInBytes, | 
|  | kDataSizeInBytes); | 
|  | } | 
|  |  | 
|  | static_assert(mojo_base::BigBuffer::kMaxInlineBytes <= | 
|  | std::numeric_limits<int>::max(), | 
|  | "Buffer size calculations may overflow int"); | 
|  |  | 
|  | // A net::IOBufferWithSize backed by a mojo_base::BigBuffer. Using BigBuffer | 
|  | // as an IOBuffer allows us to avoid a copy. For large code, this can be slow. | 
|  | class BigIOBuffer : public net::IOBufferWithSize { | 
|  | public: | 
|  | explicit BigIOBuffer(mojo_base::BigBuffer buffer) | 
|  | : net::IOBufferWithSize(nullptr, buffer.size()), | 
|  | buffer_(std::move(buffer)) { | 
|  | data_ = reinterpret_cast<char*>(buffer_.data()); | 
|  | } | 
|  | explicit BigIOBuffer(size_t size) : net::IOBufferWithSize(nullptr, size) { | 
|  | buffer_ = mojo_base::BigBuffer(size); | 
|  | data_ = reinterpret_cast<char*>(buffer_.data()); | 
|  | DCHECK(data_); | 
|  | } | 
|  | mojo_base::BigBuffer TakeBuffer() { return std::move(buffer_); } | 
|  |  | 
|  | protected: | 
|  | ~BigIOBuffer() override { | 
|  | // Storage is managed by BigBuffer. We must clear these before the base | 
|  | // class destructor runs. | 
|  | this->data_ = nullptr; | 
|  | this->size_ = 0UL; | 
|  | } | 
|  |  | 
|  | private: | 
|  | mojo_base::BigBuffer buffer_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(BigIOBuffer); | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | std::string GeneratedCodeCache::GetResourceURLFromKey(const std::string& key) { | 
|  | constexpr size_t kPrefixStringLen = base::size(kPrefix) - 1; | 
|  | // |key| may not have a prefix and separator (e.g. for deduplicated entries). | 
|  | // In that case, return an empty string. | 
|  | 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( | 
|  | GeneratedCodeCache::CacheEntryStatus status) { | 
|  | switch (cache_type_) { | 
|  | case GeneratedCodeCache::CodeCacheType::kJavaScript: | 
|  | UMA_HISTOGRAM_ENUMERATION("SiteIsolatedCodeCache.JS.Behaviour", status); | 
|  | break; | 
|  | case GeneratedCodeCache::CodeCacheType::kWebAssembly: | 
|  | UMA_HISTOGRAM_ENUMERATION("SiteIsolatedCodeCache.WASM.Behaviour", status); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Stores the information about a pending request while disk backend is | 
|  | // being initialized or another request for the same key is live. | 
|  | class GeneratedCodeCache::PendingOperation { | 
|  | public: | 
|  | PendingOperation(Operation op, | 
|  | const std::string& key, | 
|  | scoped_refptr<net::IOBufferWithSize> small_buffer, | 
|  | scoped_refptr<BigIOBuffer> large_buffer) | 
|  | : op_(op), | 
|  | key_(key), | 
|  | small_buffer_(small_buffer), | 
|  | large_buffer_(large_buffer) { | 
|  | DCHECK(Operation::kWrite == op_ || Operation::kWriteWithSHAKey == op_); | 
|  | } | 
|  |  | 
|  | PendingOperation(Operation op, | 
|  | const std::string& key, | 
|  | ReadDataCallback read_callback) | 
|  | : op_(op), key_(key), read_callback_(std::move(read_callback)) { | 
|  | DCHECK_EQ(Operation::kFetch, op_); | 
|  | } | 
|  |  | 
|  | PendingOperation(Operation op, | 
|  | const std::string& key, | 
|  | const base::Time& response_time, | 
|  | scoped_refptr<net::IOBufferWithSize> small_buffer, | 
|  | scoped_refptr<BigIOBuffer> large_buffer, | 
|  | ReadDataCallback read_callback) | 
|  | : op_(op), | 
|  | key_(key), | 
|  | response_time_(response_time), | 
|  | small_buffer_(small_buffer), | 
|  | large_buffer_(large_buffer), | 
|  | read_callback_(std::move(read_callback)) { | 
|  | DCHECK_EQ(Operation::kFetchWithSHAKey, op_); | 
|  | } | 
|  |  | 
|  | PendingOperation(Operation op, const std::string& key) : op_(op), 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_; } | 
|  | scoped_refptr<net::IOBufferWithSize> small_buffer() { return small_buffer_; } | 
|  | scoped_refptr<BigIOBuffer> large_buffer() { return large_buffer_; } | 
|  | ReadDataCallback TakeReadCallback() { return std::move(read_callback_); } | 
|  | GetBackendCallback TakeBackendCallback() { | 
|  | return std::move(backend_callback_); | 
|  | } | 
|  |  | 
|  | // These are called by Fetch operations to hold the buffers we create once the | 
|  | // entry is opened. | 
|  | 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; | 
|  | } | 
|  |  | 
|  | // This returns the site-specific response time for merged code entries. | 
|  | const base::Time& response_time() const { | 
|  | DCHECK_EQ(Operation::kFetchWithSHAKey, op_); | 
|  | return response_time_; | 
|  | } | 
|  |  | 
|  | // These are called by write and fetch operations to track buffer completions | 
|  | // and signal when the operation has finished, and whether it was successful. | 
|  | 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 std::string key_; | 
|  | const base::Time response_time_; | 
|  | 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) { | 
|  | 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 base::Time& response_time, | 
|  | mojo_base::BigBuffer data) { | 
|  | if (backend_state_ == kFailed) { | 
|  | // Silently fail the request. | 
|  | CollectStatistics(CacheEntryStatus::kError); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Reject buffers that are large enough to cause overflow problems. | 
|  | if (data.size() >= std::numeric_limits<int32_t>::max()) | 
|  | return; | 
|  |  | 
|  | scoped_refptr<net::IOBufferWithSize> small_buffer; | 
|  | scoped_refptr<BigIOBuffer> large_buffer; | 
|  | uint32_t data_size = static_cast<uint32_t>(data.size()); | 
|  | // We have three different cache entry layouts, depending on data size. | 
|  | if (data_size <= kSmallDataLimit) { | 
|  | // 1. Small | 
|  | // [stream0] response time, size, data | 
|  | // [stream1] <empty> | 
|  | small_buffer = base::MakeRefCounted<net::IOBufferWithSize>( | 
|  | kHeaderSizeInBytes + data.size()); | 
|  | // Copy |data| into the small buffer. | 
|  | memcpy(small_buffer->data() + kHeaderSizeInBytes, data.data(), data.size()); | 
|  | // Write 0 bytes and truncate stream 1 to clear any stale data. | 
|  | large_buffer = base::MakeRefCounted<BigIOBuffer>(mojo_base::BigBuffer()); | 
|  | } else if (data_size <= GetLargeDataLimit()) { | 
|  | // 2. Large | 
|  | // [stream0] response time, size | 
|  | // [stream1] data | 
|  | small_buffer = | 
|  | base::MakeRefCounted<net::IOBufferWithSize>(kHeaderSizeInBytes); | 
|  | large_buffer = base::MakeRefCounted<BigIOBuffer>(std::move(data)); | 
|  | } else { | 
|  | // 3. Very Large | 
|  | // [stream0] response time, size, checksum | 
|  | // [stream1] <empty> | 
|  | // [stream0 (checksum key entry)] <empty> | 
|  | // [stream1 (checksum key entry)] data | 
|  | uint8_t result[crypto::kSHA256Length]; | 
|  | crypto::SHA256HashString( | 
|  | base::StringPiece(reinterpret_cast<char*>(data.data()), data.size()), | 
|  | result, base::size(result)); | 
|  | std::string checksum_key = base::HexEncode(result, base::size(result)); | 
|  | small_buffer = base::MakeRefCounted<net::IOBufferWithSize>( | 
|  | kHeaderSizeInBytes + kSHAKeySizeInBytes); | 
|  | // Copy |checksum_key| into the small buffer. | 
|  | DCHECK_EQ(kSHAKeySizeInBytes, checksum_key.length()); | 
|  | memcpy(small_buffer->data() + kHeaderSizeInBytes, checksum_key.data(), | 
|  | kSHAKeySizeInBytes); | 
|  | // Write 0 bytes and truncate stream 1 to clear any stale data. | 
|  | large_buffer = base::MakeRefCounted<BigIOBuffer>(mojo_base::BigBuffer()); | 
|  |  | 
|  | // Issue another write operation for the code, with the checksum as the key | 
|  | // and nothing in the header. | 
|  | auto small_buffer2 = base::MakeRefCounted<net::IOBufferWithSize>(0); | 
|  | auto large_buffer2 = base::MakeRefCounted<BigIOBuffer>(std::move(data)); | 
|  | auto op2 = std::make_unique<PendingOperation>(Operation::kWriteWithSHAKey, | 
|  | checksum_key, small_buffer2, | 
|  | large_buffer2); | 
|  | EnqueueOperation(std::move(op2)); | 
|  | } | 
|  | WriteCommonDataHeader(small_buffer, response_time, data_size); | 
|  |  | 
|  | // Create the write operation. | 
|  | std::string key = GetCacheKey(url, origin_lock); | 
|  | auto op = std::make_unique<PendingOperation>(Operation::kWrite, key, | 
|  | small_buffer, large_buffer); | 
|  | EnqueueOperation(std::move(op)); | 
|  | } | 
|  |  | 
|  | void GeneratedCodeCache::FetchEntry(const GURL& url, | 
|  | const GURL& origin_lock, | 
|  | ReadDataCallback read_data_callback) { | 
|  | if (backend_state_ == kFailed) { | 
|  | CollectStatistics(CacheEntryStatus::kError); | 
|  | // Fail the request. | 
|  | std::move(read_data_callback).Run(base::Time(), mojo_base::BigBuffer()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string key = GetCacheKey(url, origin_lock); | 
|  | auto op = std::make_unique<PendingOperation>(Operation::kFetch, key, | 
|  | std::move(read_data_callback)); | 
|  | EnqueueOperation(std::move(op)); | 
|  | } | 
|  |  | 
|  | void GeneratedCodeCache::DeleteEntry(const GURL& url, const GURL& origin_lock) { | 
|  | if (backend_state_ == kFailed) { | 
|  | // Silently fail. | 
|  | CollectStatistics(CacheEntryStatus::kError); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string key = GetCacheKey(url, origin_lock); | 
|  | auto op = std::make_unique<PendingOperation>(Operation::kDelete, key); | 
|  | EnqueueOperation(std::move(op)); | 
|  | } | 
|  |  | 
|  | void GeneratedCodeCache::CreateBackend() { | 
|  | // Create a new Backend pointer that cleans itself if the GeneratedCodeCache | 
|  | // instance is not live when the CreateCacheBackend finishes. | 
|  | scoped_refptr<base::RefCountedData<ScopedBackendPtr>> shared_backend_ptr = | 
|  | new base::RefCountedData<ScopedBackendPtr>(); | 
|  |  | 
|  | net::CompletionOnceCallback create_backend_complete = | 
|  | base::BindOnce(&GeneratedCodeCache::DidCreateBackend, | 
|  | weak_ptr_factory_.GetWeakPtr(), shared_backend_ptr); | 
|  |  | 
|  | // If the initialization of the existing cache fails, this call would delete | 
|  | // all the contents and recreates a new one. | 
|  | int rv = disk_cache::CreateCacheBackend( | 
|  | cache_type_ == GeneratedCodeCache::CodeCacheType::kJavaScript | 
|  | ? net::GENERATED_BYTE_CODE_CACHE | 
|  | : net::GENERATED_NATIVE_CODE_CACHE, | 
|  | net::CACHE_BACKEND_SIMPLE, path_, max_size_bytes_, | 
|  | disk_cache::ResetHandling::kResetOnError, nullptr, | 
|  | &shared_backend_ptr->data, std::move(create_backend_complete)); | 
|  | if (rv != net::ERR_IO_PENDING) { | 
|  | DidCreateBackend(shared_backend_ptr, rv); | 
|  | } | 
|  | } | 
|  |  | 
|  | void GeneratedCodeCache::DidCreateBackend( | 
|  | scoped_refptr<base::RefCountedData<ScopedBackendPtr>> backend_ptr, | 
|  | int rv) { | 
|  | if (rv != net::OK) { | 
|  | backend_state_ = kFailed; | 
|  | } else { | 
|  | backend_ = std::move(backend_ptr->data); | 
|  | backend_state_ = kInitialized; | 
|  | } | 
|  | IssuePendingOperations(); | 
|  | } | 
|  |  | 
|  | void GeneratedCodeCache::EnqueueOperation( | 
|  | std::unique_ptr<PendingOperation> op) { | 
|  | if (backend_state_ != kInitialized) { | 
|  | // Insert it into the list of pending operations while the backend is | 
|  | // still being opened. | 
|  | pending_ops_.emplace(std::move(op)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | EnqueueOperationAndIssueIfNext(std::move(op)); | 
|  | } | 
|  |  | 
|  | void GeneratedCodeCache::IssuePendingOperations() { | 
|  | // Issue any operations that were received while creating the backend. | 
|  | while (!pending_ops_.empty()) { | 
|  | // Take ownership of the next PendingOperation here. |op| will either be | 
|  | // moved onto a queue in active_entries_map_ or issued and completed in | 
|  | // |DoPendingGetBackend|. | 
|  | std::unique_ptr<PendingOperation> op = std::move(pending_ops_.front()); | 
|  | pending_ops_.pop(); | 
|  | // Properly enqueue/dequeue ops for Write, Fetch, and Delete. | 
|  | if (op->operation() != Operation::kGetBackend) { | 
|  | EnqueueOperationAndIssueIfNext(std::move(op)); | 
|  | } else { | 
|  | // There is no queue for get backend operations. Issue them immediately. | 
|  | 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) { | 
|  | // Silently fail the request. | 
|  | 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(CacheEntryStatus::kError); | 
|  | CloseOperationAndIssueNext(op); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (entry_result.opened()) { | 
|  | CollectStatistics(CacheEntryStatus::kUpdate); | 
|  | } else { | 
|  | CollectStatistics(CacheEntryStatus::kCreate); | 
|  | } | 
|  |  | 
|  | disk_cache::ScopedEntryPtr entry(entry_result.ReleaseEntry()); | 
|  | // There should be a valid entry if the open was successful. | 
|  | DCHECK(entry); | 
|  |  | 
|  | // For merged entries, don't write if the entry already exists. | 
|  | 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()) { | 
|  | // Skip overwriting with identical data. | 
|  | CloseOperationAndIssueNext(op); | 
|  | return; | 
|  | } | 
|  | // Otherwise, there shouldn't be any data for this entry yet. | 
|  | DCHECK_EQ(0, small_size); | 
|  | DCHECK_EQ(0, large_size); | 
|  | } | 
|  |  | 
|  | // Write the small data first, truncating. | 
|  | 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); | 
|  | } | 
|  |  | 
|  | // Write the large data, truncating. | 
|  | 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()) { | 
|  | // The write failed; record the failure and doom the entry here. | 
|  | CollectStatistics(CacheEntryStatus::kWriteFailed); | 
|  | DoomEntry(op); | 
|  | } | 
|  | CloseOperationAndIssueNext(op); | 
|  | } | 
|  |  | 
|  | void GeneratedCodeCache::FetchEntryImpl(PendingOperation* op) { | 
|  | DCHECK(Operation::kFetch == op->operation() || | 
|  | Operation::kFetchWithSHAKey == op->operation()); | 
|  | if (backend_state_ != kInitialized) { | 
|  | op->TakeReadCallback().Run(base::Time(), mojo_base::BigBuffer()); | 
|  | CloseOperationAndIssueNext(op); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // This is a part of loading cycle and hence should run with a high priority. | 
|  | 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(CacheEntryStatus::kMiss); | 
|  | op->TakeReadCallback().Run(base::Time(), mojo_base::BigBuffer()); | 
|  | CloseOperationAndIssueNext(op); | 
|  | return; | 
|  | } | 
|  |  | 
|  | disk_cache::ScopedEntryPtr entry(entry_result.ReleaseEntry()); | 
|  | // There should be a valid entry if the open was successful. | 
|  | 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()); | 
|  | } | 
|  |  | 
|  | // Read the small data first. | 
|  | 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); | 
|  | } | 
|  |  | 
|  | // Skip the large read if data is in the small read. | 
|  | if (large_size == 0) | 
|  | return; | 
|  |  | 
|  | // Read the large data. | 
|  | 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(succeeded ? CacheEntryStatus::kHit | 
|  | : CacheEntryStatus::kMiss); | 
|  |  | 
|  | if (op->AddBufferCompletion(succeeded)) | 
|  | ReadComplete(op); | 
|  |  | 
|  | // Small reads must finish now since no large read is pending. | 
|  | 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->TakeReadCallback().Run(base::Time(), mojo_base::BigBuffer()); | 
|  | // Doom this entry since it is inaccessible. | 
|  | DoomEntry(op); | 
|  | } else { | 
|  | if (op->operation() != Operation::kFetchWithSHAKey) { | 
|  | base::Time response_time; | 
|  | uint32_t data_size = 0; | 
|  | ReadCommonDataHeader(op->small_buffer(), &response_time, &data_size); | 
|  | if (data_size <= kSmallDataLimit) { | 
|  | // Small data. Copy the data from the small buffer. | 
|  | DCHECK_EQ(0, op->large_buffer()->size()); | 
|  | mojo_base::BigBuffer data(data_size); | 
|  | memcpy(data.data(), op->small_buffer()->data() + kHeaderSizeInBytes, | 
|  | data_size); | 
|  | op->TakeReadCallback().Run(response_time, std::move(data)); | 
|  | } else if (data_size <= GetLargeDataLimit()) { | 
|  | // Large data below the merging threshold. Return the large buffer. | 
|  | op->TakeReadCallback().Run(response_time, | 
|  | op->large_buffer()->TakeBuffer()); | 
|  | } else { | 
|  | // Very large data. Create the second fetch using the checksum as key. | 
|  | DCHECK_EQ(static_cast<int>(kHeaderSizeInBytes + kSHAKeySizeInBytes), | 
|  | op->small_buffer()->size()); | 
|  | std::string checksum_key( | 
|  | 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, checksum_key, response_time, | 
|  | small_buffer, large_buffer, op->TakeReadCallback()); | 
|  | EnqueueOperation(std::move(op2)); | 
|  | } | 
|  | } else { | 
|  | // Large merged code data with no header. |op| holds the response time. | 
|  | op->TakeReadCallback().Run(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) { | 
|  | // Write, Fetch, and Delete may all doom an entry. | 
|  | DCHECK_NE(Operation::kGetBackend, op->operation()); | 
|  | // Entries shouldn't be doomed if the backend hasn't been initialized. | 
|  | DCHECK_EQ(kInitialized, backend_state_); | 
|  | CollectStatistics(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) { | 
|  | // Dequeue op, keeping it alive long enough to issue another op. | 
|  | std::unique_ptr<PendingOperation> keep_alive = DequeueOperation(op); | 
|  | IssueNextOperation(op->key()); | 
|  | } | 
|  |  | 
|  | void GeneratedCodeCache::EnqueueOperationAndIssueIfNext( | 
|  | std::unique_ptr<PendingOperation> op) { | 
|  | // GetBackend ops have no key and shouldn't be enqueued here. | 
|  | 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()); | 
|  | DCHECK(it != active_entries_map_.end()); | 
|  | DCHECK(!it->second.empty()); | 
|  | std::unique_ptr<PendingOperation> result = std::move(it->second.front()); | 
|  | // |op| should be at the front. | 
|  | DCHECK_EQ(op, result.get()); | 
|  | it->second.pop(); | 
|  | // Delete the queue if it becomes empty. | 
|  | if (it->second.empty()) { | 
|  | active_entries_map_.erase(it); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | void GeneratedCodeCache::DoPendingGetBackend(PendingOperation* op) { | 
|  | // |op| is kept alive in |IssuePendingOperations| for the duration of this | 
|  | // call. We shouldn't access |op| after returning from this function. | 
|  | DCHECK_EQ(kGetBackend, op->operation()); | 
|  | if (backend_state_ == kInitialized) { | 
|  | op->TakeBackendCallback().Run(backend_.get()); | 
|  | } else { | 
|  | DCHECK_EQ(backend_state_, kFailed); | 
|  | op->TakeBackendCallback().Run(nullptr); | 
|  | } | 
|  | } | 
|  |  | 
|  | void GeneratedCodeCache::SetLastUsedTimeForTest( | 
|  | const GURL& resource_url, | 
|  | const GURL& origin_lock, | 
|  | base::Time time, | 
|  | base::RepeatingCallback<void(void)> user_callback) { | 
|  | // This is used only for tests. So reasonable to assume that backend is | 
|  | // initialized here. All other operations handle the case when backend was not | 
|  | // yet opened. | 
|  | DCHECK_EQ(backend_state_, kInitialized); | 
|  |  | 
|  | disk_cache::EntryResultCallback callback = | 
|  | base::BindOnce(&GeneratedCodeCache::OpenCompleteForSetLastUsedForTest, | 
|  | weak_ptr_factory_.GetWeakPtr(), time, user_callback); | 
|  |  | 
|  | std::string key = GetCacheKey(resource_url, origin_lock); | 
|  | disk_cache::EntryResult result = | 
|  | backend_->OpenEntry(key, net::LOWEST, std::move(callback)); | 
|  | if (result.net_error() != net::ERR_IO_PENDING) { | 
|  | OpenCompleteForSetLastUsedForTest(time, user_callback, std::move(result)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void GeneratedCodeCache::OpenCompleteForSetLastUsedForTest( | 
|  | base::Time time, | 
|  | base::RepeatingCallback<void(void)> 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(); | 
|  | } | 
|  |  | 
|  | }  // namespace content |