Skip to content

Commit

Permalink
feat: enable code cache for custom protocols
Browse files Browse the repository at this point in the history
  • Loading branch information
zcbenz committed Dec 5, 2023
1 parent 9afeaa3 commit 035726d
Show file tree
Hide file tree
Showing 15 changed files with 526 additions and 6 deletions.
5 changes: 3 additions & 2 deletions docs/api/protocol.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "electron-test-refresh-page",
"main": "main.js"
}

0 comments on commit 035726d

Please sign in to comment.