123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
- From: Cheng Zhao <[email protected]>
- Date: Thu, 14 Dec 2023 21:16:53 +0900
- Subject: Enable V8 code cache for custom schemes
- Add a new category in ContentClient::AddAdditionalSchemes which allows
- embedders to make custom schemes allow V8 code cache.
- Chromium CL: https://chromium-review.googlesource.com/c/chromium/src/+/5019665
- diff --git a/content/browser/code_cache/generated_code_cache.cc b/content/browser/code_cache/generated_code_cache.cc
- index d9e3751608d19adc82bf67959a44926852d8eb85..3ad5a73c1326e4f9333d4f1af90bdd064e4024dd 100644
- --- a/content/browser/code_cache/generated_code_cache.cc
- +++ b/content/browser/code_cache/generated_code_cache.cc
- @@ -12,6 +12,7 @@
- #include <iostream>
- #include <string_view>
-
- +#include "base/containers/contains.h"
- #include "base/feature_list.h"
- #include "base/functional/bind.h"
- #include "base/functional/callback_helpers.h"
- @@ -36,6 +37,7 @@
- #include "net/http/http_cache.h"
- #include "third_party/blink/public/common/scheme_registry.h"
- #include "url/gurl.h"
- +#include "url/url_util.h"
-
- using storage::BigIOBuffer;
-
- @@ -48,7 +50,7 @@ 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.
- +// not opaque 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
- @@ -58,33 +60,55 @@ void CheckValidKeys(const GURL& resource_url,
- GeneratedCodeCache::CodeCacheType cache_type) {
- // If the resource url is invalid don't cache the code.
- DCHECK(resource_url.is_valid());
- - bool resource_url_is_chrome_or_chrome_untrusted =
- +
- + // There are 3 kind of URL scheme compatible for the `resource_url`.
- + // 1. http: and https: URLs.
- + // 2. chrome: and chrome-untrusted: URLs.
- + // 3. URLs whose scheme are allowed by the content/ embedder.
- + const bool resource_url_http = resource_url.SchemeIsHTTPOrHTTPS();
- + const bool resource_url_webui =
- 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.scheme()));
- -
- - // |origin_lock| should be either empty or should have
- - // Http/Https/chrome/chrome-untrusted 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.
- - bool origin_lock_is_chrome_or_chrome_untrusted =
- +
- + const bool resource_url_embedder =
- + base::Contains(url::GetCodeCacheSchemes(), resource_url.scheme());
- + DCHECK(resource_url_http || resource_url_webui || resource_url_embedder);
- +
- + // |origin_lock| should be either empty or should have code cache allowed
- + // schemes (http/https/chrome/chrome-untrusted or other custom schemes added
- + // by url::AddCodeCacheScheme), and it should not be a URL with opaque
- + // origin. Empty origin_locks are allowed when the renderer is not locked to
- + // an origin.
- + const bool origin_lock_empty = origin_lock.is_empty();
- + const bool origin_lock_for_http = origin_lock.SchemeIsHTTPOrHTTPS();
- + const bool origin_lock_for_webui =
- 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.scheme())) &&
- - !url::Origin::Create(origin_lock).opaque()));
- -
- - // The chrome and chrome-untrusted schemes are only used with the WebUI
- - // code cache type.
- - DCHECK_EQ(origin_lock_is_chrome_or_chrome_untrusted,
- - cache_type == GeneratedCodeCache::kWebUIJavaScript);
- - DCHECK_EQ(resource_url_is_chrome_or_chrome_untrusted,
- - cache_type == GeneratedCodeCache::kWebUIJavaScript);
- + const bool origin_lock_for_embedder =
- + base::Contains(url::GetCodeCacheSchemes(), origin_lock.scheme());
- +
- + DCHECK(origin_lock_empty || ((origin_lock_for_http || origin_lock_for_webui ||
- + origin_lock_for_embedder) &&
- + !url::Origin::Create(origin_lock).opaque()));
- +
- + // The webui schemes are only used with their dedicated code cache type.
- + switch (cache_type) {
- + case GeneratedCodeCache::kJavaScript:
- + case GeneratedCodeCache::kWebAssembly:
- + DCHECK(!origin_lock_for_webui);
- + DCHECK(!resource_url_webui);
- + break;
- + case GeneratedCodeCache::kWebUIJavaScript:
- + DCHECK(origin_lock_for_webui);
- + DCHECK(resource_url_webui);
- + break;
- + }
- +
- + // The custom schemes share the cache type with http(s).
- + if (origin_lock_for_embedder || resource_url_embedder) {
- + DCHECK(cache_type == GeneratedCodeCache::kJavaScript ||
- + cache_type == GeneratedCodeCache::kWebAssembly);
- + }
- }
-
- // Generates the cache key for the given |resource_url|, |origin_lock| and
- diff --git a/content/browser/code_cache/generated_code_cache.h b/content/browser/code_cache/generated_code_cache.h
- index e0cdc785d2557bc79bde98728c23c239ed8d0961..74e0acbea1e0c18a6ac8971170efc945ca58f4ed 100644
- --- a/content/browser/code_cache/generated_code_cache.h
- +++ b/content/browser/code_cache/generated_code_cache.h
- @@ -52,12 +52,14 @@ class CONTENT_EXPORT GeneratedCodeCache {
- // Cache type. Used for collecting statistics for JS and Wasm in separate
- // buckets.
- enum CodeCacheType {
- - // JavaScript from http(s) pages.
- + // JavaScript from pages of http(s) schemes or custom schemes registered by
- + // url::AddCodeCacheScheme.
- 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.
- + // WebAssembly from pages of http(s) schemes or custom schemes registered by
- + // url::AddCodeCacheScheme. 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
- diff --git a/content/browser/code_cache/generated_code_cache_browsertest.cc b/content/browser/code_cache/generated_code_cache_browsertest.cc
- index b6dd7405a5c9275ab699d4b347759427b30ef594..253918a2e54c98ce0075bce4e1a52134032ce367 100644
- --- a/content/browser/code_cache/generated_code_cache_browsertest.cc
- +++ b/content/browser/code_cache/generated_code_cache_browsertest.cc
- @@ -8,13 +8,18 @@
- #include "content/browser/code_cache/generated_code_cache.h"
- #include "content/browser/code_cache/generated_code_cache_context.h"
- #include "content/browser/renderer_host/code_cache_host_impl.h"
- +#include "content/browser/storage_partition_impl.h"
- +#include "content/common/url_schemes.h"
- #include "content/public/browser/browser_context.h"
- +#include "content/public/browser/browser_thread.h"
- #include "content/public/browser/storage_partition.h"
- #include "content/public/test/browser_test.h"
- #include "content/public/test/browser_test_utils.h"
- #include "content/public/test/content_browser_test.h"
- #include "content/public/test/content_browser_test_utils.h"
- +#include "content/public/test/test_browser_context.h"
- #include "content/shell/browser/shell.h"
- +#include "content/test/test_content_client.h"
- #include "net/dns/mock_host_resolver.h"
- #include "third_party/blink/public/common/features.h"
- #include "third_party/blink/public/common/page/v8_compile_hints_histograms.h"
- @@ -23,6 +28,8 @@ namespace content {
-
- namespace {
-
- +const std::string kCodeCacheScheme = "test-code-cache";
- +
- bool SupportsSharedWorker() {
- #if BUILDFLAG(IS_ANDROID)
- // SharedWorkers are not enabled on Android. https://crbug.com/154571
- @@ -714,4 +721,82 @@ IN_PROC_BROWSER_TEST_F(LocalCompileHintsBrowserTest, LocalCompileHints) {
- }
- }
-
- +class CodeCacheInCustomSchemeBrowserTest : public ContentBrowserTest,
- + public TestContentClient {
- + public:
- + CodeCacheInCustomSchemeBrowserTest() {
- + SetContentClient(this);
- + ReRegisterContentSchemesForTests();
- + }
- +
- + ~CodeCacheInCustomSchemeBrowserTest() override { SetContentClient(nullptr); }
- +
- + private:
- + void AddAdditionalSchemes(Schemes* schemes) override {
- + schemes->standard_schemes.push_back(kCodeCacheScheme);
- + schemes->code_cache_schemes.push_back(kCodeCacheScheme);
- + }
- +
- + url::ScopedSchemeRegistryForTests scheme_registry_;
- +};
- +
- +IN_PROC_BROWSER_TEST_F(CodeCacheInCustomSchemeBrowserTest,
- + AllowedCustomSchemeCanGenerateCodeCache) {
- + StoragePartitionImpl* partition =
- + static_cast<StoragePartitionImpl*>(shell()
- + ->web_contents()
- + ->GetBrowserContext()
- + ->GetDefaultStoragePartition());
- + scoped_refptr<GeneratedCodeCacheContext> context =
- + partition->GetGeneratedCodeCacheContext();
- + EXPECT_NE(context, nullptr);
- +
- + GURL url(kCodeCacheScheme + "://host4/script.js");
- + GURL origin(kCodeCacheScheme + "://host1:1/");
- + ASSERT_TRUE(url.is_valid());
- + ASSERT_TRUE(origin.is_valid());
- + std::string data("SomeData");
- +
- + // Add a code cache entry for the custom scheme.
- + base::test::TestFuture<void> add_entry_future;
- + GeneratedCodeCacheContext::RunOrPostTask(
- + context.get(), FROM_HERE,
- + base::BindOnce(
- + [](scoped_refptr<GeneratedCodeCacheContext> context, const GURL& url,
- + const GURL& origin, const std::string& data,
- + base::OnceClosure callback) {
- + context->generated_js_code_cache()->WriteEntry(
- + url, origin, net::NetworkIsolationKey(), base::Time::Now(),
- + std::vector<uint8_t>(data.begin(), data.end()));
- + GetUIThreadTaskRunner({})->PostTask(FROM_HERE, std::move(callback));
- + },
- + context, url, origin, data, add_entry_future.GetCallback()));
- + ASSERT_TRUE(add_entry_future.Wait());
- +
- + // Get the code cache entry.
- + base::test::TestFuture<std::string> get_entry_future;
- + GeneratedCodeCacheContext::RunOrPostTask(
- + context.get(), FROM_HERE,
- + base::BindOnce(
- + [](scoped_refptr<GeneratedCodeCacheContext> context, const GURL& url,
- + const GURL& origin,
- + base::OnceCallback<void(std::string)> callback) {
- + context->generated_js_code_cache()->FetchEntry(
- + url, origin, net::NetworkIsolationKey(),
- + base::BindOnce(
- + [](base::OnceCallback<void(std::string)> callback,
- + const base::Time& response_time,
- + mojo_base::BigBuffer buffer) {
- + std::string data(buffer.data(),
- + buffer.data() + buffer.size());
- + GetUIThreadTaskRunner({})->PostTask(
- + FROM_HERE, base::BindOnce(std::move(callback), data));
- + },
- + std::move(callback)));
- + },
- + context, url, origin, get_entry_future.GetCallback()));
- + ASSERT_TRUE(get_entry_future.Wait());
- + ASSERT_EQ(data, get_entry_future.Get<0>());
- +}
- +
- } // namespace content
- diff --git a/content/browser/renderer_host/code_cache_host_impl.cc b/content/browser/renderer_host/code_cache_host_impl.cc
- index 15e731756530e684b583f25a3f2bdf4af3653d54..f68a97b3ebf1d2151fc19950d41b15915a334e9f 100644
- --- a/content/browser/renderer_host/code_cache_host_impl.cc
- +++ b/content/browser/renderer_host/code_cache_host_impl.cc
- @@ -6,6 +6,7 @@
-
- #include <utility>
-
- +#include "base/containers/contains.h"
- #include "base/functional/bind.h"
- #include "base/functional/callback_helpers.h"
- #include "base/metrics/histogram_functions.h"
- @@ -29,6 +30,7 @@
- #include "third_party/blink/public/common/scheme_registry.h"
- #include "url/gurl.h"
- #include "url/origin.h"
- +#include "url/url_util.h"
-
- using blink::mojom::CacheStorageError;
-
- @@ -36,6 +38,11 @@ namespace content {
-
- namespace {
-
- +bool ProcessLockURLIsCodeCacheScheme(const ProcessLock& process_lock) {
- + return base::Contains(url::GetCodeCacheSchemes(),
- + process_lock.lock_url().scheme());
- +}
- +
- bool CheckSecurityForAccessingCodeCacheData(
- const GURL& resource_url,
- int render_process_id,
- @@ -46,39 +53,57 @@ bool CheckSecurityForAccessingCodeCacheData(
-
- // Code caching is only allowed for http(s) and chrome/chrome-untrusted
- // scripts. Furthermore, there is no way for http(s) pages to load chrome or
- + // Code caching is only allowed for scripts from:
- + // 1. http: and https: schemes.
- + // 2. chrome: and chrome-untrusted: schemes.
- + // 3. Schemes registered by content/ embedder via url::AddCodeCacheScheme.
- + //
- + // Furthermore, we know there are no way for http(s) pages to load chrome or
- // chrome-untrusted scripts, so any http(s) page attempting to store data
- // about a chrome or chrome-untrusted script would be an indication of
- // suspicious activity.
- - if (resource_url.SchemeIs(content::kChromeUIScheme) ||
- - resource_url.SchemeIs(content::kChromeUIUntrustedScheme)) {
- - if (!process_lock.is_locked_to_site()) {
- - // We can't tell for certain whether this renderer is doing something
- - // malicious, but we don't trust it enough to store data.
- - return false;
- - }
- + if (resource_url.SchemeIsHTTPOrHTTPS()) {
- if (process_lock.matches_scheme(url::kHttpScheme) ||
- process_lock.matches_scheme(url::kHttpsScheme)) {
- - if (operation == CodeCacheHostImpl::Operation::kWrite) {
- + return true;
- + }
- + // Pages in custom schemes like isolated-app: are allowed to load http(s)
- + // resources.
- + if (ProcessLockURLIsCodeCacheScheme(process_lock)) {
- + return true;
- + }
- + // It is possible for WebUI pages to include open-web content, but such
- + // usage is rare and we've decided that reasoning about security is easier
- + // if the WebUI code cache includes only WebUI scripts.
- + return false;
- + }
- +
- + if (resource_url.SchemeIs(kChromeUIScheme) ||
- + resource_url.SchemeIs(kChromeUIUntrustedScheme)) {
- + if (process_lock.matches_scheme(kChromeUIScheme) ||
- + process_lock.matches_scheme(kChromeUIUntrustedScheme)) {
- + return true;
- + }
- + if (operation == CodeCacheHostImpl::Operation::kWrite) {
- + if (process_lock.matches_scheme(url::kHttpScheme) ||
- + process_lock.matches_scheme(url::kHttpsScheme)) {
- mojo::ReportBadMessage("HTTP(S) pages cannot cache WebUI code");
- }
- + if (ProcessLockURLIsCodeCacheScheme(process_lock)) {
- + mojo::ReportBadMessage(
- + "Page whose scheme are allowed by content/ embedders cannot cache "
- + "WebUI code. Did the embedder misconfigured content/?");
- + }
- return false;
- }
- // Other schemes which might successfully load chrome or chrome-untrusted
- // scripts, such as the PDF viewer, are unsupported but not considered
- - // dangerous.
- - return process_lock.matches_scheme(content::kChromeUIScheme) ||
- - process_lock.matches_scheme(content::kChromeUIUntrustedScheme);
- + // dangerous. Similarly, the process might not be locked to a site.
- + return false;
- }
- - if (resource_url.SchemeIsHTTPOrHTTPS() ||
- - blink::CommonSchemeRegistry::IsExtensionScheme(resource_url.scheme())) {
- - if (process_lock.matches_scheme(content::kChromeUIScheme) ||
- - process_lock.matches_scheme(content::kChromeUIUntrustedScheme)) {
- - // It is possible for WebUI pages to include open-web content, but such
- - // usage is rare and we've decided that reasoning about security is easier
- - // if the WebUI code cache includes only WebUI scripts.
- - return false;
- - }
- - return true;
- +
- + if (base::Contains(url::GetCodeCacheSchemes(), resource_url.scheme())) {
- + return ProcessLockURLIsCodeCacheScheme(process_lock);
- }
-
- if (operation == CodeCacheHostImpl::Operation::kWrite) {
- @@ -427,6 +452,7 @@ std::optional<GURL> CodeCacheHostImpl::GetSecondaryKeyForCodeCache(
- process_lock.matches_scheme(url::kHttpsScheme) ||
- process_lock.matches_scheme(content::kChromeUIScheme) ||
- process_lock.matches_scheme(content::kChromeUIUntrustedScheme) ||
- + ProcessLockURLIsCodeCacheScheme(process_lock) ||
- blink::CommonSchemeRegistry::IsExtensionScheme(
- process_lock.lock_url().scheme())) {
- return process_lock.lock_url();
- diff --git a/content/common/url_schemes.cc b/content/common/url_schemes.cc
- index ce9644d33fe83379127b01bf9a2b1c4badc3bc7c..fd486d4637ae4766ed78571dee7f9cebbd809f38 100644
- --- a/content/common/url_schemes.cc
- +++ b/content/common/url_schemes.cc
- @@ -98,6 +98,14 @@ void RegisterContentSchemes(bool should_lock_registry) {
- for (auto& scheme : schemes.empty_document_schemes)
- url::AddEmptyDocumentScheme(scheme.c_str());
-
- + for (auto& scheme : schemes.code_cache_schemes) {
- + CHECK_NE(scheme, kChromeUIScheme);
- + CHECK_NE(scheme, kChromeUIUntrustedScheme);
- + CHECK_NE(scheme, url::kHttpScheme);
- + CHECK_NE(scheme, url::kHttpsScheme);
- + url::AddCodeCacheScheme(scheme.c_str());
- + }
- +
- #if BUILDFLAG(IS_ANDROID)
- if (schemes.allow_non_standard_schemes_in_origins)
- url::EnableNonStandardSchemesForAndroidWebView();
- diff --git a/content/public/common/content_client.h b/content/public/common/content_client.h
- index 9dc2d5a33858da7c31fd87bbbabe3899301fa52d..ebf0bb23b9aedb7bf9eb8af52b4756dba452183e 100644
- --- a/content/public/common/content_client.h
- +++ b/content/public/common/content_client.h
- @@ -142,6 +142,9 @@ class CONTENT_EXPORT ContentClient {
- // Registers a URL scheme as strictly empty documents, allowing them to
- // commit synchronously.
- std::vector<std::string> empty_document_schemes;
- + // Registers a URL scheme whose js and wasm scripts have V8 code cache
- + // enabled.
- + std::vector<std::string> code_cache_schemes;
- // Registers a URL scheme as extension scheme.
- std::vector<std::string> extension_schemes;
- // Registers a URL scheme with a predefined default custom handler.
- diff --git a/url/url_util.cc b/url/url_util.cc
- index ce5225e121f5db611d95ba5dfa88cea42f32076b..d402cc9db212abcedd62018bb242eb524cc6acf2 100644
- --- a/url/url_util.cc
- +++ b/url/url_util.cc
- @@ -135,6 +135,9 @@ struct SchemeRegistry {
- kMaterializedViewScheme,
- };
-
- + // Embedder schemes that have V8 code cache enabled in js and wasm scripts.
- + std::vector<std::string> code_cache_schemes = {};
- +
- // Schemes with a predefined default custom handler.
- std::vector<SchemeWithHandler> predefined_handler_schemes;
-
- @@ -716,6 +719,15 @@ const std::vector<std::string>& GetEmptyDocumentSchemes() {
- return GetSchemeRegistry().empty_document_schemes;
- }
-
- +void AddCodeCacheScheme(const char* new_scheme) {
- + DoAddScheme(new_scheme,
- + &GetSchemeRegistryWithoutLocking()->code_cache_schemes);
- +}
- +
- +const std::vector<std::string>& GetCodeCacheSchemes() {
- + return GetSchemeRegistry().code_cache_schemes;
- +}
- +
- void AddPredefinedHandlerScheme(const char* new_scheme, const char* handler) {
- DoAddSchemeWithHandler(
- new_scheme, handler,
- diff --git a/url/url_util.h b/url/url_util.h
- index e39a44057cebce7cdf90bcb02a7463e88bd271b4..e80b81d2ddeb2ec201b143d86bec6ee54ca49afc 100644
- --- a/url/url_util.h
- +++ b/url/url_util.h
- @@ -115,6 +115,15 @@ COMPONENT_EXPORT(URL) const std::vector<std::string>& GetCSPBypassingSchemes();
- COMPONENT_EXPORT(URL) void AddEmptyDocumentScheme(const char* new_scheme);
- COMPONENT_EXPORT(URL) const std::vector<std::string>& GetEmptyDocumentSchemes();
-
- +// Adds an application-defined scheme to the list of schemes that have V8 code
- +// cache enabled for the js and wasm scripts.
- +// The WebUI schemes (chrome/chrome-untrusted) do not belong to this list, as
- +// they are treated as a separate cache type for security purpose.
- +// The http(s) schemes do not belong to this list neither, they always have V8
- +// code cache enabled.
- +COMPONENT_EXPORT(URL) void AddCodeCacheScheme(const char* new_scheme);
- +COMPONENT_EXPORT(URL) const std::vector<std::string>& GetCodeCacheSchemes();
- +
- // Adds a scheme with a predefined default handler.
- //
- // This pair of strings must be normalized protocol handler parameters as
|