Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable code cache for custom protocols #40544

Merged
merged 1 commit into from Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/api/protocol.md
Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions docs/api/session.md
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions docs/api/structures/custom-scheme.md
Expand Up @@ -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.
1 change: 1 addition & 0 deletions patches/chromium/.patches
Expand Up @@ -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
395 changes: 395 additions & 0 deletions patches/chromium/feat_allow_code_cache_in_custom_schemes.patch

Large diffs are not rendered by default.

28 changes: 27 additions & 1 deletion shell/browser/api/electron_api_protocol.cc
Expand Up @@ -32,6 +32,9 @@ std::vector<std::string> g_standard_schemes;
// List of registered custom streaming schemes.
std::vector<std::string> g_streaming_schemes;

// Schemes that support V8 code cache.
std::vector<std::string> g_code_cache_schemes;

struct SchemeOptions {
bool standard = false;
bool secure = false;
Expand All @@ -40,6 +43,7 @@ struct SchemeOptions {
bool supportFetchAPI = false;
bool corsEnabled = false;
bool stream = false;
bool codeCache = false;
};

struct CustomScheme {
Expand Down Expand Up @@ -71,6 +75,7 @@ struct Converter<CustomScheme> {
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;
}
Expand All @@ -82,10 +87,14 @@ namespace electron::api {

gin::WrapperInfo Protocol::kWrapperInfo = {gin::kEmbedderNativeGin};

std::vector<std::string> GetStandardSchemes() {
const std::vector<std::string>& GetStandardSchemes() {
return g_standard_schemes;
}

const std::vector<std::string>& 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.
Expand All @@ -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<std::string> secure_schemes, cspbypassing_schemes, fetch_schemes,
service_worker_schemes, cors_schemes;
for (const auto& custom_scheme : custom_schemes) {
Expand Down Expand Up @@ -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<std::string> schemes) {
if (schemes.empty())
return;
// Add the schemes to command line switches, so child processes can also
// register them.
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
Expand All @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion shell/browser/api/electron_api_protocol.h
Expand Up @@ -22,7 +22,8 @@ class ProtocolRegistry;

namespace api {

std::vector<std::string> GetStandardSchemes();
const std::vector<std::string>& GetStandardSchemes();
const std::vector<std::string>& GetCodeCacheSchemes();

void AddServiceWorkerScheme(const std::string& scheme);

Expand Down
5 changes: 3 additions & 2 deletions shell/browser/electron_browser_client.cc
Expand Up @@ -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 ||
Expand Down Expand Up @@ -694,7 +695,7 @@ ElectronBrowserClient::CreateWindowForVideoPictureInPicture(

void ElectronBrowserClient::GetAdditionalAllowedSchemesForFileSystem(
std::vector<std::string>* 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());
Expand Down
3 changes: 3 additions & 0 deletions shell/common/options_switches.cc
Expand Up @@ -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";

Expand Down
1 change: 1 addition & 0 deletions shell/common/options_switches.h
Expand Up @@ -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[];

Expand Down
7 changes: 7 additions & 0 deletions shell/renderer/renderer_client_base.cc
Expand Up @@ -276,6 +276,13 @@ void RendererClientBase::RenderThreadStarted() {
blink::SchemeRegistry::RegisterURLSchemeAsBypassingContentSecurityPolicy(
WTF::String::FromUTF8(scheme.data(), scheme.length()));

std::vector<std::string> 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()) {
Expand Down
27 changes: 27 additions & 0 deletions spec/api-protocol-spec.ts
Expand Up @@ -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);

Expand Down
9 changes: 9 additions & 0 deletions spec/fixtures/apps/refresh-page/main.html
@@ -0,0 +1,9 @@
<html>
<body>
<!-- Use mocha which has a large enough js file -->
<script src="mocha.js"></script>
<script>
mocha.setup('bdd');
</script>
</body>
</html>
38 changes: 38 additions & 0 deletions 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();
});
4 changes: 4 additions & 0 deletions spec/fixtures/apps/refresh-page/package.json
@@ -0,0 +1,4 @@
{
"name": "electron-test-refresh-page",
"main": "main.js"
}