diff --git a/docs/api/protocol.md b/docs/api/protocol.md index 9b093fc89078..1a9b446a20ca 100644 --- a/docs/api/protocol.md +++ b/docs/api/protocol.md @@ -61,8 +61,9 @@ The `protocol` module has the following methods: module gets emitted and can be called only once. Registers the `scheme` as standard, secure, bypasses content security policy for -resources, allows registering ServiceWorker, supports fetch API, and streaming -video/audio. Specify a privilege with the value of `true` to enable the capability. +resources, allows registering ServiceWorker, supports fetch API, streaming +video/audio, and V8 code cache. Specify a privilege with the value of `true` to +enable the capability. An example of registering a privileged scheme, that bypasses Content Security Policy: diff --git a/docs/api/session.md b/docs/api/session.md index f8f4367d6e0b..1a48b44d5a93 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -1356,6 +1356,10 @@ registered. Sets the directory to store the generated JS [code cache](https://v8.dev/blog/code-caching-for-devs) for this session. The directory is not required to be created by the user before this call, the runtime will create if it does not exist otherwise will use the existing directory. If directory cannot be created, then code cache will not be used and all operations related to code cache will fail silently inside the runtime. By default, the directory will be `Code Cache` under the respective user data folder. +Note that by default code cache is only enabled for http(s) URLs, to enable code +cache for custom protocols, `codeCache: true` and `standard: true` must be +specified when registering the protocol. + #### `ses.clearCodeCaches(options)` * `options` Object diff --git a/docs/api/structures/custom-scheme.md b/docs/api/structures/custom-scheme.md index 402a17506a1b..3476ede7a43d 100644 --- a/docs/api/structures/custom-scheme.md +++ b/docs/api/structures/custom-scheme.md @@ -9,3 +9,5 @@ * `supportFetchAPI` boolean (optional) - Default false. * `corsEnabled` boolean (optional) - Default false. * `stream` boolean (optional) - Default false. + * `codeCache` boolean (optional) - Enable V8 code cache for the scheme, only + works when `standard` is also set to true. Default false. diff --git a/patches/chromium/.patches b/patches/chromium/.patches index 9831b3005da1..ac0e9c6e6ce3 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -128,3 +128,4 @@ feat_allow_passing_of_objecttemplate_to_objecttemplatebuilder.patch chore_remove_check_is_test_on_script_injection_tracker.patch fix_restore_original_resize_performance_on_macos.patch fix_font_flooding_in_dev_tools.patch +feat_allow_code_cache_in_custom_schemes.patch diff --git a/patches/chromium/feat_allow_code_cache_in_custom_schemes.patch b/patches/chromium/feat_allow_code_cache_in_custom_schemes.patch new file mode 100644 index 000000000000..182b5fbce368 --- /dev/null +++ b/patches/chromium/feat_allow_code_cache_in_custom_schemes.patch @@ -0,0 +1,395 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cheng Zhao +Date: Thu, 26 Oct 2023 11:07:47 +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 65ac47b199b3f6d37fe78495eeb3d3598c4add8d..5230bf2cf1387ca73b34e0be2e0cffecd46a3653 100644 +--- a/content/browser/code_cache/generated_code_cache.cc ++++ b/content/browser/code_cache/generated_code_cache.cc +@@ -6,6 +6,7 @@ + + #include + ++#include "base/containers/contains.h" + #include "base/feature_list.h" + #include "base/functional/bind.h" + #include "base/functional/callback_helpers.h" +@@ -24,6 +25,7 @@ + #include "net/base/url_util.h" + #include "net/http/http_cache.h" + #include "url/gurl.h" ++#include "url/url_util.h" + + using storage::BigIOBuffer; + +@@ -46,24 +48,37 @@ 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_allows_code_cache = ++ base::Contains(url::GetCodeCacheSchemes(), resource_url.scheme()); + bool resource_url_is_chrome_or_chrome_untrusted = + resource_url.SchemeIs(content::kChromeUIScheme) || + resource_url.SchemeIs(content::kChromeUIUntrustedScheme); + DCHECK(resource_url.SchemeIsHTTPOrHTTPS() || ++ resource_url_allows_code_cache || + resource_url_is_chrome_or_chrome_untrusted); + +- // |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. ++ // |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. ++ bool origin_lock_allows_code_cache = ++ base::Contains(url::GetCodeCacheSchemes(), origin_lock.scheme()); + 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_allows_code_cache || + origin_lock_is_chrome_or_chrome_untrusted) && + !url::Origin::Create(origin_lock).opaque())); + ++ // The custom schemes share the cache type with http(s). ++ if (origin_lock_allows_code_cache || resource_url_allows_code_cache) { ++ DCHECK(cache_type == GeneratedCodeCache::kJavaScript || ++ cache_type == GeneratedCodeCache::kWebAssembly); ++ } ++ + // The chrome and chrome-untrusted schemes are only used with the WebUI + // code cache type. + DCHECK_EQ(origin_lock_is_chrome_or_chrome_untrusted, +diff --git a/content/browser/code_cache/generated_code_cache.h b/content/browser/code_cache/generated_code_cache.h +index f5c5ff2c89489257003dfe3284ee9de9f517c99b..fdd2e2483171c4d43963590200817dac27d22cf9 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 672b9bb14cd493b05d1e27019cda30c5269bf46f..f4093315dea8feb4184adbfd4c398768a6fb197d 100644 +--- a/content/browser/code_cache/generated_code_cache_browsertest.cc ++++ b/content/browser/code_cache/generated_code_cache_browsertest.cc +@@ -2,21 +2,32 @@ + // Use of this source code is governed by a BSD-style license that can be + // found in the LICENSE file. + ++#include "base/test/test_future.h" + #include "base/test/metrics/histogram_tester.h" + #include "base/test/scoped_feature_list.h" + #include "base/test/test_future.h" ++#include "components/services/storage/storage_service_impl.h" + #include "content/browser/code_cache/generated_code_cache.h" ++#include "content/browser/code_cache/generated_code_cache_context.h" ++#include "content/browser/storage_partition_impl.h" ++#include "content/common/url_schemes.h" ++#include "content/public/browser/browser_thread.h" + #include "content/public/test/browser_test.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 "url/url_util.h" + + 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 +@@ -427,4 +438,80 @@ IN_PROC_BROWSER_TEST_P(CodeCacheBrowserTest, + } + } + ++class CodeCacheInCustomSchemeBrowserTest : public ContentBrowserTest { ++ public: ++ CodeCacheInCustomSchemeBrowserTest() { ++ SetContentClient(&test_content_client_); ++ ReRegisterContentSchemesForTests(); ++ } ++ ++ private: ++ class CustomSchemeContentClient : public TestContentClient { ++ public: ++ void AddAdditionalSchemes(Schemes* schemes) override { ++ schemes->standard_schemes.push_back(kCodeCacheScheme); ++ schemes->code_cache_schemes.push_back(kCodeCacheScheme); ++ } ++ }; ++ ++ CustomSchemeContentClient test_content_client_; ++ url::ScopedSchemeRegistryForTests scheme_registry_; ++}; ++ ++IN_PROC_BROWSER_TEST_F(CodeCacheInCustomSchemeBrowserTest, ++ AllowedCustomSchemeCanGenerateCodeCache) { ++ // Create browser context and get code cache context. ++ base::ScopedAllowBlockingForTesting allow_blocking; ++ TestBrowserContext browser_context; ++ StoragePartitionImpl* partition = static_cast( ++ browser_context.GetDefaultStoragePartition()); ++ scoped_refptr 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 add_entry_future; ++ GeneratedCodeCacheContext::RunOrPostTask( ++ context.get(), FROM_HERE, ++ base::BindOnce([](scoped_refptr 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(data.begin(), data.end())); ++ content::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 get_entry_future; ++ GeneratedCodeCacheContext::RunOrPostTask( ++ context.get(), FROM_HERE, ++ base::BindOnce([](scoped_refptr context, ++ const GURL& url, ++ const GURL& origin, ++ base::OnceCallback callback) { ++ context->generated_js_code_cache()->FetchEntry( ++ url, origin, net::NetworkIsolationKey(), ++ base::BindOnce([](base::OnceCallback callback, ++ const base::Time& response_time, ++ mojo_base::BigBuffer buffer) { ++ std::string data(buffer.data(), buffer.data() + buffer.size()); ++ content::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 6b9e5065dc570b506c4c2606d536319d98684e12..9d1f337b9c9890b6b7afda40bf2f829ff2a25bfd 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 + ++#include "base/containers/contains.h" + #include "base/functional/bind.h" + #include "base/functional/callback_helpers.h" + #include "base/metrics/histogram_functions.h" +@@ -28,6 +29,7 @@ + #include "third_party/blink/public/common/cache_storage/cache_storage_utils.h" + #include "url/gurl.h" + #include "url/origin.h" ++#include "url/url_util.h" + + using blink::mojom::CacheStorageError; + +@@ -40,6 +42,11 @@ enum class Operation { + kWrite, + }; + ++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, + Operation operation) { +@@ -47,11 +54,12 @@ bool CheckSecurityForAccessingCodeCacheData(const GURL& resource_url, + ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock( + render_process_id); + +- // 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 +- // 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. ++ // Code caching is only allowed for scripts from open-web (http/https and ++ // custom schemes registered with url::AddCodeCacheScheme) and ++ // chrome/chrome-untrusted schemes. Furthermore, there is no way for ++ // open-web pages to load chrome or chrome-untrusted scripts, so any ++ // open-web 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()) { +@@ -60,9 +68,10 @@ bool CheckSecurityForAccessingCodeCacheData(const GURL& resource_url, + return false; + } + if (process_lock.matches_scheme(url::kHttpScheme) || +- process_lock.matches_scheme(url::kHttpsScheme)) { ++ process_lock.matches_scheme(url::kHttpsScheme) || ++ ProcessLockURLIsCodeCacheScheme(process_lock)) { + if (operation == Operation::kWrite) { +- mojo::ReportBadMessage("HTTP(S) pages cannot cache WebUI code"); ++ mojo::ReportBadMessage("Open-web pages cannot cache WebUI code"); + } + return false; + } +@@ -72,7 +81,16 @@ bool CheckSecurityForAccessingCodeCacheData(const GURL& resource_url, + return process_lock.matches_scheme(content::kChromeUIScheme) || + process_lock.matches_scheme(content::kChromeUIUntrustedScheme); + } +- if (resource_url.SchemeIsHTTPOrHTTPS()) { ++ if (base::Contains(url::GetCodeCacheSchemes(), resource_url.scheme()) && ++ (process_lock.matches_scheme(url::kHttpScheme) || ++ process_lock.matches_scheme(url::kHttpsScheme))) { ++ // While custom schemes registered with url::AddCodeCacheScheme are ++ // considered as open-web pages, we still do not trust http(s) pages ++ // loading resources from custom schemes. ++ return false; ++ } ++ if (resource_url.SchemeIsHTTPOrHTTPS() || ++ base::Contains(url::GetCodeCacheSchemes(), 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 +@@ -136,15 +154,17 @@ absl::optional GetSecondaryKeyForCodeCache(const GURL& resource_url, + return absl::nullopt; + + // Case 3: process_lock_url is used to enfore site-isolation in code caches. +- // Http/https/chrome schemes are safe to be used as a secondary key. Other +- // schemes could be enabled if they are known to be safe and if it is +- // required to cache code from those origins. ++ // Code cache enabled schemes (http/https/chrome/chrome-untrusted and custom ++ // schemes registered with url::AddCodeCacheScheme) are safe to be used as a ++ // secondary key. Other schemes could be enabled if they are known to be safe ++ // and if it is required to cache code from those origins. + // + // file:// URLs will have a "file:" process lock and would thus share a + // cache across all file:// URLs. That would likely be ok for security, but + // since this case is not performance sensitive we will keep things simple and +- // limit the cache to http/https/chrome/chrome-untrusted processes. +- if (process_lock.matches_scheme(url::kHttpScheme) || ++ // limit the cache to processes of code cache enabled schemes. ++ if (ProcessLockURLIsCodeCacheScheme(process_lock) || ++ process_lock.matches_scheme(url::kHttpScheme) || + process_lock.matches_scheme(url::kHttpsScheme) || + process_lock.matches_scheme(content::kChromeUIScheme) || + process_lock.matches_scheme(content::kChromeUIUntrustedScheme)) { +diff --git a/content/common/url_schemes.cc b/content/common/url_schemes.cc +index ce9644d33fe83379127b01bf9a2b1c4badc3bc7c..532b14e013b9b65ba390f09e8bb8934bb278a0d8 100644 +--- a/content/common/url_schemes.cc ++++ b/content/common/url_schemes.cc +@@ -98,6 +98,9 @@ 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) ++ 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 5d1484651fb8c3e03337665d3d5342ba51df3154..d4432a660d6c5a5e937dedabb7e4b71b87c9504b 100644 +--- a/content/public/common/content_client.h ++++ b/content/public/common/content_client.h +@@ -139,6 +139,9 @@ class CONTENT_EXPORT ContentClient { + // Registers a URL scheme as strictly empty documents, allowing them to + // commit synchronously. + std::vector empty_document_schemes; ++ // Registers a URL scheme whose js and wasm scripts have V8 code cache ++ // enabled. ++ std::vector code_cache_schemes; + // Registers a URL scheme as extension scheme. + std::vector 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 9258cfcfada47aafe6ba20c648187947fec72372..a1834e543d27d46265af0c2133acac79b6c840e2 100644 +--- a/url/url_util.cc ++++ b/url/url_util.cc +@@ -114,6 +114,9 @@ struct SchemeRegistry { + kAboutScheme, + }; + ++ // Embedder schemes that have V8 code cache enabled in js and wasm scripts. ++ std::vector code_cache_schemes = {}; ++ + // Schemes with a predefined default custom handler. + std::vector predefined_handler_schemes; + +@@ -659,6 +662,15 @@ const std::vector& GetEmptyDocumentSchemes() { + return GetSchemeRegistry().empty_document_schemes; + } + ++void AddCodeCacheScheme(const char* new_scheme) { ++ DoAddScheme(new_scheme, ++ &GetSchemeRegistryWithoutLocking()->code_cache_schemes); ++} ++ ++const std::vector& 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 8c94c7a4f6d5f653d326199e5b43452f969911d4..40dcdf9d680f9d345c09426da48b37f288234244 100644 +--- a/url/url_util.h ++++ b/url/url_util.h +@@ -115,6 +115,15 @@ COMPONENT_EXPORT(URL) const std::vector& GetCSPBypassingSchemes(); + COMPONENT_EXPORT(URL) void AddEmptyDocumentScheme(const char* new_scheme); + COMPONENT_EXPORT(URL) const std::vector& 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 and can not load scripts from schemes in this list. ++COMPONENT_EXPORT(URL) void AddCodeCacheScheme(const char* new_scheme); ++COMPONENT_EXPORT(URL) const std::vector& GetCodeCacheSchemes(); ++ + // Adds a scheme with a predefined default handler. + // + // This pair of strings must be normalized protocol handler parameters as diff --git a/shell/browser/api/electron_api_protocol.cc b/shell/browser/api/electron_api_protocol.cc index 444964e79a75..24f2cf1e178f 100644 --- a/shell/browser/api/electron_api_protocol.cc +++ b/shell/browser/api/electron_api_protocol.cc @@ -32,6 +32,9 @@ std::vector g_standard_schemes; // List of registered custom streaming schemes. std::vector g_streaming_schemes; +// Schemes that support V8 code cache. +std::vector g_code_cache_schemes; + struct SchemeOptions { bool standard = false; bool secure = false; @@ -40,6 +43,7 @@ struct SchemeOptions { bool supportFetchAPI = false; bool corsEnabled = false; bool stream = false; + bool codeCache = false; }; struct CustomScheme { @@ -71,6 +75,7 @@ struct Converter { opt.Get("supportFetchAPI", &(out->options.supportFetchAPI)); opt.Get("corsEnabled", &(out->options.corsEnabled)); opt.Get("stream", &(out->options.stream)); + opt.Get("codeCache", &(out->options.codeCache)); } return true; } @@ -82,10 +87,14 @@ namespace electron::api { gin::WrapperInfo Protocol::kWrapperInfo = {gin::kEmbedderNativeGin}; -std::vector GetStandardSchemes() { +const std::vector& GetStandardSchemes() { return g_standard_schemes; } +const std::vector& GetCodeCacheSchemes() { + return g_code_cache_schemes; +} + void AddServiceWorkerScheme(const std::string& scheme) { // There is no API to add service worker scheme, but there is an API to // return const reference to the schemes vector. @@ -104,6 +113,15 @@ void RegisterSchemesAsPrivileged(gin_helper::ErrorThrower thrower, return; } + for (const auto& custom_scheme : custom_schemes) { + if (custom_scheme.options.codeCache && !custom_scheme.options.standard) { + thrower.ThrowError( + "Code cache can only be enabled when the custom scheme is registered " + "as standard scheme."); + return; + } + } + std::vector secure_schemes, cspbypassing_schemes, fetch_schemes, service_worker_schemes, cors_schemes; for (const auto& custom_scheme : custom_schemes) { @@ -137,10 +155,16 @@ void RegisterSchemesAsPrivileged(gin_helper::ErrorThrower thrower, if (custom_scheme.options.stream) { g_streaming_schemes.push_back(custom_scheme.scheme); } + if (custom_scheme.options.codeCache) { + g_code_cache_schemes.push_back(custom_scheme.scheme); + url::AddCodeCacheScheme(custom_scheme.scheme.c_str()); + } } const auto AppendSchemesToCmdLine = [](const char* switch_name, std::vector schemes) { + if (schemes.empty()) + return; // Add the schemes to command line switches, so child processes can also // register them. base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( @@ -158,6 +182,8 @@ void RegisterSchemesAsPrivileged(gin_helper::ErrorThrower thrower, g_standard_schemes); AppendSchemesToCmdLine(electron::switches::kStreamingSchemes, g_streaming_schemes); + AppendSchemesToCmdLine(electron::switches::kCodeCacheSchemes, + g_code_cache_schemes); } namespace { diff --git a/shell/browser/api/electron_api_protocol.h b/shell/browser/api/electron_api_protocol.h index 8fc493473b8a..7db7b66c3ce1 100644 --- a/shell/browser/api/electron_api_protocol.h +++ b/shell/browser/api/electron_api_protocol.h @@ -22,7 +22,8 @@ class ProtocolRegistry; namespace api { -std::vector GetStandardSchemes(); +const std::vector& GetStandardSchemes(); +const std::vector& GetCodeCacheSchemes(); void AddServiceWorkerScheme(const std::string& scheme); diff --git a/shell/browser/electron_browser_client.cc b/shell/browser/electron_browser_client.cc index 93d227241eba..c4d514fe0d71 100644 --- a/shell/browser/electron_browser_client.cc +++ b/shell/browser/electron_browser_client.cc @@ -526,7 +526,8 @@ void ElectronBrowserClient::AppendExtraCommandLineSwitches( switches::kStandardSchemes, switches::kEnableSandbox, switches::kSecureSchemes, switches::kBypassCSPSchemes, switches::kCORSSchemes, switches::kFetchSchemes, - switches::kServiceWorkerSchemes, switches::kStreamingSchemes}; + switches::kServiceWorkerSchemes, switches::kStreamingSchemes, + switches::kCodeCacheSchemes}; command_line->CopySwitchesFrom(*base::CommandLine::ForCurrentProcess(), kCommonSwitchNames); if (process_type == ::switches::kUtilityProcess || @@ -694,7 +695,7 @@ ElectronBrowserClient::CreateWindowForVideoPictureInPicture( void ElectronBrowserClient::GetAdditionalAllowedSchemesForFileSystem( std::vector* additional_schemes) { - auto schemes_list = api::GetStandardSchemes(); + const auto& schemes_list = api::GetStandardSchemes(); if (!schemes_list.empty()) additional_schemes->insert(additional_schemes->end(), schemes_list.begin(), schemes_list.end()); diff --git a/shell/common/options_switches.cc b/shell/common/options_switches.cc index 4f1ec42cad50..f353e23b2589 100644 --- a/shell/common/options_switches.cc +++ b/shell/common/options_switches.cc @@ -227,6 +227,9 @@ const char kCORSSchemes[] = "cors-schemes"; // Register schemes as streaming responses. const char kStreamingSchemes[] = "streaming-schemes"; +// Register schemes as supporting V8 code cache. +const char kCodeCacheSchemes[] = "code-cache-schemes"; + // The browser process app model ID const char kAppUserModelId[] = "app-user-model-id"; diff --git a/shell/common/options_switches.h b/shell/common/options_switches.h index c529b7d02dc4..bb7936a3b14e 100644 --- a/shell/common/options_switches.h +++ b/shell/common/options_switches.h @@ -113,6 +113,7 @@ extern const char kBypassCSPSchemes[]; extern const char kFetchSchemes[]; extern const char kCORSSchemes[]; extern const char kStreamingSchemes[]; +extern const char kCodeCacheSchemes[]; extern const char kAppUserModelId[]; extern const char kAppPath[]; diff --git a/shell/renderer/renderer_client_base.cc b/shell/renderer/renderer_client_base.cc index e811b66805e3..81e25bf3b902 100644 --- a/shell/renderer/renderer_client_base.cc +++ b/shell/renderer/renderer_client_base.cc @@ -276,6 +276,13 @@ void RendererClientBase::RenderThreadStarted() { blink::SchemeRegistry::RegisterURLSchemeAsBypassingContentSecurityPolicy( WTF::String::FromUTF8(scheme.data(), scheme.length())); + std::vector code_cache_schemes_list = + ParseSchemesCLISwitch(command_line, switches::kCodeCacheSchemes); + for (const auto& scheme : code_cache_schemes_list) { + blink::WebSecurityPolicy::RegisterURLSchemeAsCodeCacheWithHashing( + blink::WebString::FromASCII(scheme)); + } + // Allow file scheme to handle service worker by default. // FIXME(zcbenz): Can this be moved elsewhere? if (electron::fuses::IsGrantFileProtocolExtraPrivilegesEnabled()) { diff --git a/spec/api-protocol-spec.ts b/spec/api-protocol-spec.ts index 6e3659034a1c..19ba61de12b8 100644 --- a/spec/api-protocol-spec.ts +++ b/spec/api-protocol-spec.ts @@ -1090,6 +1090,33 @@ describe('protocol module', () => { } }); + describe('protocol.registerSchemesAsPrivileged codeCache', function () { + const temp = require('temp').track(); + const appPath = path.join(fixturesPath, 'apps', 'refresh-page'); + + let w: BrowserWindow; + let codeCachePath: string; + beforeEach(async () => { + w = new BrowserWindow({ show: false }); + codeCachePath = temp.path(); + }); + + afterEach(async () => { + await closeWindow(w); + w = null as unknown as BrowserWindow; + }); + + it('code cache in custom protocol is disabled by default', async () => { + ChildProcess.spawnSync(process.execPath, [appPath, 'false', codeCachePath]); + expect(fs.readdirSync(path.join(codeCachePath, 'js')).length).to.equal(2); + }); + + it('codeCache:true enables codeCache in custom protocol', async () => { + ChildProcess.spawnSync(process.execPath, [appPath, 'true', codeCachePath]); + expect(fs.readdirSync(path.join(codeCachePath, 'js')).length).to.above(2); + }); + }); + describe('handle', () => { afterEach(closeAllWindows); diff --git a/spec/fixtures/apps/refresh-page/main.html b/spec/fixtures/apps/refresh-page/main.html new file mode 100644 index 000000000000..5aceb68b7b31 --- /dev/null +++ b/spec/fixtures/apps/refresh-page/main.html @@ -0,0 +1,9 @@ + + + + + + + diff --git a/spec/fixtures/apps/refresh-page/main.js b/spec/fixtures/apps/refresh-page/main.js new file mode 100644 index 000000000000..f4ada26db399 --- /dev/null +++ b/spec/fixtures/apps/refresh-page/main.js @@ -0,0 +1,38 @@ +const path = require('node:path'); +const { once } = require('node:events'); +const { pathToFileURL } = require('node:url'); +const { BrowserWindow, app, protocol, net, session } = require('electron'); + +if (process.argv.length < 4) { + console.error('Must pass allow_code_cache code_cache_dir'); + process.exit(1); +} + +protocol.registerSchemesAsPrivileged([ + { + scheme: 'atom', + privileges: { + standard: true, + codeCache: process.argv[2] === 'true' + } + } +]); + +app.once('ready', async () => { + const codeCachePath = process.argv[3]; + session.defaultSession.setCodeCachePath(codeCachePath); + + protocol.handle('atom', (request) => { + let { pathname } = new URL(request.url); + if (pathname === '/mocha.js') { pathname = path.resolve(__dirname, '../../../node_modules/mocha/mocha.js'); } else { pathname = path.join(__dirname, pathname); } + return net.fetch(pathToFileURL(pathname).toString()); + }); + + const win = new BrowserWindow({ show: false }); + win.loadURL('atom://host/main.html'); + await once(win.webContents, 'did-finish-load'); + // Reload to generate code cache. + win.reload(); + await once(win.webContents, 'did-finish-load'); + app.exit(); +}); diff --git a/spec/fixtures/apps/refresh-page/package.json b/spec/fixtures/apps/refresh-page/package.json new file mode 100644 index 000000000000..0d7231e0ecb6 --- /dev/null +++ b/spec/fixtures/apps/refresh-page/package.json @@ -0,0 +1,4 @@ +{ + "name": "electron-test-refresh-page", + "main": "main.js" +}