| // 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. |
| |
| #ifndef CONTENT_BROWSER_CODE_CACHE_GENERATED_CODE_CACHE_H_ |
| #define CONTENT_BROWSER_CODE_CACHE_GENERATED_CODE_CACHE_H_ |
| |
| #include <map> |
| #include <queue> |
| |
| #include "base/containers/queue.h" |
| #include "base/files/file_path.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/timer/timer.h" |
| #include "content/browser/code_cache/simple_lru_cache.h" |
| #include "content/common/content_export.h" |
| #include "mojo/public/cpp/base/big_buffer.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/network_isolation_key.h" |
| #include "net/disk_cache/disk_cache.h" |
| #include "url/origin.h" |
| |
| namespace content { |
| |
| // Cache for storing generated code from the renderer on the disk. This cache |
| // uses |resource_url| + |origin_lock| as a key for storing the generated code. |
| // |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. This is used to enforce site isolation policy on cached code. |
| // 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. |
| // |
| // The key is generated by concatenating the serialized url and origin lock |
| // with a separator in between. The separator is non-valid URL characters, to |
| // prevent any attacks by crafting the URLs. |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. |
| // |
| // This uses a simple disk_cache backend. It just stores one data stream and |
| // stores response_time + generated code as one data blob. |
| // |
| // There exists one cache per storage partition and is owned by the storage |
| // partition. This cache is created, accessed and destroyed on the I/O |
| // thread. |
| class CONTENT_EXPORT GeneratedCodeCache { |
| public: |
| using ReadDataCallback = |
| base::OnceCallback<void(const base::Time&, mojo_base::BigBuffer data)>; |
| using GetBackendCallback = base::OnceCallback<void(disk_cache::Backend*)>; |
| |
| // Cache type. Used for collecting statistics for JS and Wasm in separate |
| // buckets. |
| enum CodeCacheType { |
| // JavaScript from http(s) pages. |
| kJavaScript, |
| |
| // WebAssembly from http(s) pages. This cache allows more total size and |
| // more size per item than the JavaScript cache, since some |
| // WebAssembly programs are very large. |
| kWebAssembly, |
| |
| // JavaScript from chrome and chrome-untrusted pages. The resource URLs are |
| // limited to only those fetched via chrome and chrome-untrusted schemes. |
| // The cache size is limited to disk_cache::kMaxWebUICodeCacheSize. |
| // Deduplication of very large items is disabled in this cache. |
| kWebUIJavaScript, |
| }; |
| |
| // Used for collecting statistics about cache behaviour. |
| // Since it's uploaded to UMA, its values must never change. |
| enum CacheEntryStatus : uint8_t { |
| kHit, |
| kMiss, |
| kClear, |
| kUpdate, |
| kCreate, |
| kError, |
| kIncompleteEntry, |
| kWriteFailed, |
| kMaxValue = kWriteFailed |
| }; |
| |
| // Returns the resource URL from the key. The key has the format prefix + |
| // resource URL + separator + requesting origin. This function extracts and |
| // returns resource URL from the key, or the empty string if key is invalid. |
| static std::string GetResourceURLFromKey(const std::string& key); |
| |
| // Creates a GeneratedCodeCache with the specified path and the maximum size. |
| // If |max_size_bytes| is 0, then disk_cache picks a default size based on |
| // some heuristics. |
| GeneratedCodeCache(const base::FilePath& path, |
| int max_size_bytes, |
| CodeCacheType cache_type); |
| |
| GeneratedCodeCache(const GeneratedCodeCache&) = delete; |
| GeneratedCodeCache& operator=(const GeneratedCodeCache&) = delete; |
| |
| ~GeneratedCodeCache(); |
| |
| // Runs the callback with a raw pointer to the backend. If we could not create |
| // the backend then it will return a null. This runs the callback |
| // synchronously if the backend is already open or asynchronously on the |
| // completion of a pending backend creation. |
| void GetBackend(GetBackendCallback callback); |
| |
| // Writes data to the cache. If there is an entry corresponding to |
| // <|resource_url|, |origin_lock|> this overwrites the existing data. If |
| // there is no entry it creates a new one. |
| void WriteEntry(const GURL& resource_url, |
| const GURL& origin_lock, |
| const net::NetworkIsolationKey& nik, |
| const base::Time& response_time, |
| mojo_base::BigBuffer data); |
| |
| // Fetch entry corresponding to <resource_url, origin_lock> from the cache |
| // and return it using the ReadDataCallback. |
| void FetchEntry(const GURL& resource_url, |
| const GURL& origin_lock, |
| const net::NetworkIsolationKey& nik, |
| ReadDataCallback); |
| |
| // Delete the entry corresponding to <resource_url, origin_lock> |
| void DeleteEntry(const GURL& resource_url, |
| const GURL& origin_lock, |
| const net::NetworkIsolationKey& nik); |
| |
| // Should be only used for tests. Sets the last accessed timestamp of an |
| // entry. |
| void SetLastUsedTimeForTest(const GURL& resource_url, |
| const GURL& origin_lock, |
| const net::NetworkIsolationKey& nik, |
| base::Time time, |
| base::OnceClosure callback); |
| |
| // Clears the in-memory cache. |
| void ClearInMemoryCache(); |
| |
| const base::FilePath& path() const { return path_; } |
| |
| private: |
| class PendingOperation; |
| |
| // State of the backend. |
| enum BackendState { kInitializing, kInitialized, kFailed }; |
| |
| // The operation requested. |
| enum Operation { |
| kFetch, |
| kFetchWithSHAKey, |
| kWrite, |
| kWriteWithSHAKey, |
| kDelete, |
| kGetBackend |
| }; |
| |
| // Data streams corresponding to each entry. |
| enum { kSmallDataStream = 0, kLargeDataStream = 1 }; |
| |
| // Creates a simple_disk_cache backend. |
| void CreateBackend(); |
| void DidCreateBackend(disk_cache::BackendResult result); |
| |
| // Adds operation to the appropriate queue. |
| void EnqueueOperation(std::unique_ptr<PendingOperation> op); |
| |
| // Issues ops that were received while the backend was being initialized. |
| void IssuePendingOperations(); |
| void IssueOperation(PendingOperation* op); |
| |
| // Writes entry to cache. |
| void WriteEntryImpl(PendingOperation* op); |
| void OpenCompleteForWrite(PendingOperation* op, |
| disk_cache::EntryResult result); |
| void WriteSmallBufferComplete(PendingOperation* op, int rv); |
| void WriteLargeBufferComplete(PendingOperation* op, int rv); |
| void WriteComplete(PendingOperation* op); |
| |
| // Fetches entry from cache. |
| void FetchEntryImpl(PendingOperation* op); |
| void OpenCompleteForRead(PendingOperation* op, |
| disk_cache::EntryResult result); |
| void ReadSmallBufferComplete(PendingOperation* op, int rv); |
| void ReadLargeBufferComplete(PendingOperation* op, int rv); |
| void ReadComplete(PendingOperation* op); |
| |
| // Deletes entry from cache. |
| void DeleteEntryImpl(PendingOperation* op); |
| |
| void DoomEntry(PendingOperation* op); |
| |
| // Issues the next operation on the queue for |key|. |
| void IssueNextOperation(const std::string& key); |
| // Removes |op| and issues the next operation on its queue. |
| void CloseOperationAndIssueNext(PendingOperation* op); |
| |
| // Enqueues the operation issues it if there are no pending operations for |
| // its key. |
| void EnqueueOperationAndIssueIfNext(std::unique_ptr<PendingOperation> op); |
| // Dequeues the operation and transfers ownership to caller. |
| std::unique_ptr<PendingOperation> DequeueOperation(PendingOperation* op); |
| |
| void DoPendingGetBackend(PendingOperation* op); |
| |
| void OpenCompleteForSetLastUsedForTest(base::Time time, |
| base::OnceClosure callback, |
| disk_cache::EntryResult result); |
| |
| void CollectStatistics(GeneratedCodeCache::CacheEntryStatus status); |
| |
| // Whether very large cache entries are deduplicated in this cache. |
| // Deduplication is disabled in the WebUI code cache, as an additional defense |
| // against privilege escalation in case there is a bug in the deduplication |
| // logic. |
| bool IsDeduplicationEnabled() const; |
| |
| bool ShouldDeduplicateEntry(uint32_t data_size) const; |
| |
| // 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 uses |
| // obsolete formats. These reads should fail and be doomed as soon as |
| // possible. |
| bool IsValidHeader(scoped_refptr<net::IOBufferWithSize> small_buffer) const; |
| |
| void ReportPeriodicalHistograms(); |
| |
| std::unique_ptr<disk_cache::Backend> backend_; |
| BackendState backend_state_; |
| |
| // Queue for operations received while initializing the backend. |
| using PendingOperationQueue = base::queue<std::unique_ptr<PendingOperation>>; |
| PendingOperationQueue pending_ops_; |
| |
| // Map from key to queue of pending operations. |
| std::map<std::string, PendingOperationQueue> active_entries_map_; |
| |
| base::FilePath path_; |
| int max_size_bytes_; |
| CodeCacheType cache_type_; |
| |
| // A hypothetical memory-backed code cache. Used to collect UMAs. |
| SimpleLruCache lru_cache_; |
| base::RepeatingTimer histograms_timer_; |
| static constexpr int64_t kLruCacheCapacity = 50 * 1024 * 1024; |
| |
| base::WeakPtrFactory<GeneratedCodeCache> weak_ptr_factory_{this}; |
| }; |
| |
| } // namespace content |
| |
| #endif // CONTENT_BROWSER_CODE_CACHE_GENERATED_CODE_CACHE_H_ |