From 9483e714c46cfa134209691b29b4ab5cf9fdb2ac Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 9 May 2022 23:26:57 +0900 Subject: [PATCH] feat: allow customizing browser data location (#33554) * feat: redirect Electron/Chromium cache location * fix: network services should also use browserData * test: browserData * chore: no need to explicitly create dir * feat: browserData => sessionData * test: check existings of specific items * docs: add background on userData and sessionData Co-authored-by: emmanuel.kimmerlin@thomsonreuters.com --- docs/api/app.md | 18 +++++-- shell/app/electron_main_delegate.cc | 5 +- shell/browser/api/electron_api_app.cc | 2 + shell/browser/browser_process_impl.cc | 2 +- shell/browser/electron_browser_client.cc | 8 +-- shell/browser/electron_browser_context.cc | 3 +- shell/browser/electron_browser_main_parts.cc | 2 +- shell/browser/ui/devtools_manager_delegate.cc | 6 +-- shell/common/electron_paths.h | 3 +- spec-main/api-app-spec.ts | 50 ++++++++++++++++++- spec-main/fixtures/apps/set-path/main.js | 43 ++++++++++++++++ spec-main/fixtures/apps/set-path/package.json | 4 ++ 12 files changed, 128 insertions(+), 18 deletions(-) create mode 100644 spec-main/fixtures/apps/set-path/main.js create mode 100644 spec-main/fixtures/apps/set-path/package.json diff --git a/docs/api/app.md b/docs/api/app.md index e10e20fcd5d22..16207e255c60e 100755 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -635,8 +635,18 @@ Returns `string` - The current application directory. * `%APPDATA%` on Windows * `$XDG_CONFIG_HOME` or `~/.config` on Linux * `~/Library/Application Support` on macOS - * `userData` The directory for storing your app's configuration files, which by - default it is the `appData` directory appended with your app's name. + * `userData` The directory for storing your app's configuration files, which + by default is the `appData` directory appended with your app's name. By + convention files storing user data should be written to this directory, and + it is not recommended to write large files here because some environments + may backup this directory to cloud storage. + * `sessionData` The directory for storing data generated by `Session`, such + as localStorage, cookies, disk cache, downloaded dictionaries, network + state, devtools files. By default this points to `userData`. Chromium may + write very large disk cache here, so if your app does not rely on browser + storage like localStorage or cookies to save user data, it is recommended + to set this directory to other locations to avoid polluting the `userData` + directory. * `temp` Temporary directory. * `exe` The current executable file. * `module` The `libchromiumcontent` library. @@ -686,9 +696,9 @@ In that case, the directory should be created with `fs.mkdirSync` or similar. You can only override paths of a `name` defined in `app.getPath`. -By default, web pages' cookies and caches will be stored under the `userData` +By default, web pages' cookies and caches will be stored under the `sessionData` directory. If you want to change this location, you have to override the -`userData` path before the `ready` event of the `app` module is emitted. +`sessionData` path before the `ready` event of the `app` module is emitted. ### `app.getVersion()` diff --git a/shell/app/electron_main_delegate.cc b/shell/app/electron_main_delegate.cc index e1769670fb8ba..a0ca93fc63e30 100644 --- a/shell/app/electron_main_delegate.cc +++ b/shell/app/electron_main_delegate.cc @@ -134,11 +134,14 @@ bool ElectronPathProvider(int key, base::FilePath* result) { break; case chrome::DIR_APP_DICTIONARIES: // TODO(nornagon): can we just default to using Chrome's logic here? - if (!base::PathService::Get(chrome::DIR_USER_DATA, &cur)) + if (!base::PathService::Get(DIR_SESSION_DATA, &cur)) return false; cur = cur.Append(base::FilePath::FromUTF8Unsafe("Dictionaries")); create_dir = true; break; + case DIR_SESSION_DATA: + // By default and for backward, equivalent to DIR_USER_DATA. + return base::PathService::Get(chrome::DIR_USER_DATA, result); case DIR_USER_CACHE: { #if BUILDFLAG(IS_POSIX) int parent_key = base::DIR_CACHE; diff --git a/shell/browser/api/electron_api_app.cc b/shell/browser/api/electron_api_app.cc index 5964a4933f302..6bd1d052e6cf0 100644 --- a/shell/browser/api/electron_api_app.cc +++ b/shell/browser/api/electron_api_app.cc @@ -473,6 +473,8 @@ IconLoader::IconSize GetIconSizeByString(const std::string& size) { int GetPathConstant(const std::string& name) { if (name == "appData") return DIR_APP_DATA; + else if (name == "sessionData") + return DIR_SESSION_DATA; else if (name == "userData") return chrome::DIR_USER_DATA; else if (name == "cache") diff --git a/shell/browser/browser_process_impl.cc b/shell/browser/browser_process_impl.cc index 5f0bdc532ab6d..31653e104f8c5 100644 --- a/shell/browser/browser_process_impl.cc +++ b/shell/browser/browser_process_impl.cc @@ -110,7 +110,7 @@ void BrowserProcessImpl::PostEarlyInitialization() { // Only use a persistent prefs store when cookie encryption is enabled as that // is the only key that needs it base::FilePath prefs_path; - CHECK(base::PathService::Get(chrome::DIR_USER_DATA, &prefs_path)); + CHECK(base::PathService::Get(electron::DIR_SESSION_DATA, &prefs_path)); prefs_path = prefs_path.Append(FILE_PATH_LITERAL("Local State")); base::ThreadRestrictions::ScopedAllowIO allow_io; scoped_refptr user_pref_store = diff --git a/shell/browser/electron_browser_client.cc b/shell/browser/electron_browser_client.cc index 6db8fbec2119b..f1d4d25077aac 100644 --- a/shell/browser/electron_browser_client.cc +++ b/shell/browser/electron_browser_client.cc @@ -1139,11 +1139,11 @@ void ElectronBrowserClient::OnNetworkServiceCreated( std::vector ElectronBrowserClient::GetNetworkContextsParentDirectory() { - base::FilePath user_data_dir; - base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); - DCHECK(!user_data_dir.empty()); + base::FilePath session_data; + base::PathService::Get(DIR_SESSION_DATA, &session_data); + DCHECK(!session_data.empty()); - return {user_data_dir}; + return {session_data}; } std::string ElectronBrowserClient::GetProduct() { diff --git a/shell/browser/electron_browser_context.cc b/shell/browser/electron_browser_context.cc index eb640eb3517c2..5d3c74ad3d054 100644 --- a/shell/browser/electron_browser_context.cc +++ b/shell/browser/electron_browser_context.cc @@ -120,8 +120,7 @@ ElectronBrowserContext::ElectronBrowserContext(const std::string& partition, base::StringToInt(command_line->GetSwitchValueASCII(switches::kDiskCacheSize), &max_cache_size_); - CHECK(base::PathService::Get(chrome::DIR_USER_DATA, &path_)); - + base::PathService::Get(DIR_SESSION_DATA, &path_); if (!in_memory && !partition.empty()) path_ = path_.Append(FILE_PATH_LITERAL("Partitions")) .Append(base::FilePath::FromUTF8Unsafe( diff --git a/shell/browser/electron_browser_main_parts.cc b/shell/browser/electron_browser_main_parts.cc index defb91ab6e1da..5245db81376d3 100644 --- a/shell/browser/electron_browser_main_parts.cc +++ b/shell/browser/electron_browser_main_parts.cc @@ -501,7 +501,7 @@ void ElectronBrowserMainParts::PostCreateMainMessageLoop() { // https://source.chromium.org/chromium/chromium/src/+/master:chrome/common/chrome_switches.cc;l=689;drc=9d82515060b9b75fa941986f5db7390299669ef1 config->should_use_preference = command_line.HasSwitch(::switches::kEnableEncryptionSelection); - base::PathService::Get(chrome::DIR_USER_DATA, &config->user_data_path); + base::PathService::Get(DIR_SESSION_DATA, &config->user_data_path); OSCrypt::SetConfig(std::move(config)); #endif #if BUILDFLAG(IS_POSIX) diff --git a/shell/browser/ui/devtools_manager_delegate.cc b/shell/browser/ui/devtools_manager_delegate.cc index 40773b35b2f21..a9aac3cace877 100644 --- a/shell/browser/ui/devtools_manager_delegate.cc +++ b/shell/browser/ui/devtools_manager_delegate.cc @@ -91,10 +91,10 @@ const char kBrowserCloseMethod[] = "Browser.close"; // static void DevToolsManagerDelegate::StartHttpHandler() { - base::FilePath user_dir; - base::PathService::Get(chrome::DIR_USER_DATA, &user_dir); + base::FilePath session_data; + base::PathService::Get(DIR_SESSION_DATA, &session_data); content::DevToolsAgentHost::StartRemoteDebuggingServer( - CreateSocketFactory(), user_dir, base::FilePath()); + CreateSocketFactory(), session_data, base::FilePath()); } DevToolsManagerDelegate::DevToolsManagerDelegate() = default; diff --git a/shell/common/electron_paths.h b/shell/common/electron_paths.h index 6854e97d6ae48..c614f992940bb 100644 --- a/shell/common/electron_paths.h +++ b/shell/common/electron_paths.h @@ -23,7 +23,8 @@ enum { PATH_START = 11000, DIR_USER_CACHE = PATH_START, // Directory where user cache can be written. - DIR_APP_LOGS, // Directory where app logs live + DIR_APP_LOGS, // Directory where app logs live. + DIR_SESSION_DATA, // Where cookies, localStorage are stored. #if BUILDFLAG(IS_WIN) DIR_RECENT, // Directory where recent files live diff --git a/spec-main/api-app-spec.ts b/spec-main/api-app-spec.ts index 0389b70931a07..f1af0bc512159 100644 --- a/spec-main/api-app-spec.ts +++ b/spec-main/api-app-spec.ts @@ -3,7 +3,7 @@ import * as cp from 'child_process'; import * as https from 'https'; import * as http from 'http'; import * as net from 'net'; -import * as fs from 'fs'; +import * as fs from 'fs-extra'; import * as path from 'path'; import { promisify } from 'util'; import { app, BrowserWindow, Menu, session, net as electronNet } from 'electron/main'; @@ -1078,6 +1078,54 @@ describe('app module', () => { expect(() => { app.getPath(badPath as any); }).to.throw(); }); + + describe('sessionData', () => { + const appPath = path.join(__dirname, 'fixtures', 'apps', 'set-path'); + const appName = fs.readJsonSync(path.join(appPath, 'package.json')).name; + const userDataPath = path.join(app.getPath('appData'), appName); + const tempBrowserDataPath = path.join(app.getPath('temp'), appName); + + const sessionFiles = [ + 'Preferences', + 'Code Cache', + 'Local Storage', + 'IndexedDB', + 'Service Worker' + ]; + const hasSessionFiles = (dir: string) => { + for (const file of sessionFiles) { + if (!fs.existsSync(path.join(dir, file))) { + return false; + } + } + return true; + }; + + beforeEach(() => { + fs.removeSync(userDataPath); + fs.removeSync(tempBrowserDataPath); + }); + + it('writes to userData by default', () => { + expect(hasSessionFiles(userDataPath)).to.equal(false); + cp.spawnSync(process.execPath, [appPath]); + expect(hasSessionFiles(userDataPath)).to.equal(true); + }); + + it('can be changed', () => { + expect(hasSessionFiles(userDataPath)).to.equal(false); + cp.spawnSync(process.execPath, [appPath, 'sessionData', tempBrowserDataPath]); + expect(hasSessionFiles(userDataPath)).to.equal(false); + expect(hasSessionFiles(tempBrowserDataPath)).to.equal(true); + }); + + it('changing userData affects default sessionData', () => { + expect(hasSessionFiles(userDataPath)).to.equal(false); + cp.spawnSync(process.execPath, [appPath, 'userData', tempBrowserDataPath]); + expect(hasSessionFiles(userDataPath)).to.equal(false); + expect(hasSessionFiles(tempBrowserDataPath)).to.equal(true); + }); + }); }); describe('setAppLogsPath(path)', () => { diff --git a/spec-main/fixtures/apps/set-path/main.js b/spec-main/fixtures/apps/set-path/main.js new file mode 100644 index 0000000000000..efc6a624eef5b --- /dev/null +++ b/spec-main/fixtures/apps/set-path/main.js @@ -0,0 +1,43 @@ +const http = require('http'); +const { app, ipcMain, BrowserWindow } = require('electron'); + +if (process.argv.length > 3) { + app.setPath(process.argv[2], process.argv[3]); +} + +const html = ` + +`; + +const js = 'console.log("From service worker")'; + +app.once('ready', () => { + ipcMain.on('success', () => { + app.quit(); + }); + + const server = http.createServer((request, response) => { + if (request.url === '/') { + response.writeHead(200, { 'Content-Type': 'text/html' }); + response.end(html); + } else if (request.url === '/sw.js') { + response.writeHead(200, { 'Content-Type': 'text/javascript' }); + response.end(js); + } + }).listen(0, '127.0.0.1', () => { + const serverUrl = 'http://127.0.0.1:' + server.address().port; + const mainWindow = new BrowserWindow({ show: false, webPreferences: { webSecurity: true, nodeIntegration: true, contextIsolation: false } }); + mainWindow.loadURL(serverUrl); + }); +}); diff --git a/spec-main/fixtures/apps/set-path/package.json b/spec-main/fixtures/apps/set-path/package.json new file mode 100644 index 0000000000000..84edf0f3a389a --- /dev/null +++ b/spec-main/fixtures/apps/set-path/package.json @@ -0,0 +1,4 @@ +{ + "name": "electron-test-set-path", + "main": "main.js" +}