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 <emmanuel.kimmerlin@thomsonreuters.com>
This commit is contained in:
		
					parent
					
						
							
								03e68e2efe
							
						
					
				
			
			
				commit
				
					
						9483e714c4
					
				
			
		
					 12 changed files with 128 additions and 18 deletions
				
			
		|  | @ -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()` | ||||
| 
 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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") | ||||
|  |  | |||
|  | @ -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<JsonPrefStore> user_pref_store = | ||||
|  |  | |||
|  | @ -1139,11 +1139,11 @@ void ElectronBrowserClient::OnNetworkServiceCreated( | |||
| 
 | ||||
| std::vector<base::FilePath> | ||||
| 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() { | ||||
|  |  | |||
|  | @ -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( | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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)', () => { | ||||
|  |  | |||
							
								
								
									
										43
									
								
								spec-main/fixtures/apps/set-path/main.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								spec-main/fixtures/apps/set-path/main.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 = ` | ||||
| <script> | ||||
| async function main() { | ||||
|   localStorage.setItem('myCat', 'Tom') | ||||
|   const db = indexedDB.open('db-name', 1) | ||||
|   await new Promise(resolve => db.onsuccess = resolve) | ||||
|   await navigator.serviceWorker.register('sw.js', {scope: './'}) | ||||
| } | ||||
| 
 | ||||
| main().then(() => { | ||||
|   require('electron').ipcRenderer.send('success') | ||||
| }) | ||||
| </script> | ||||
| `;
 | ||||
| 
 | ||||
| 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); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										4
									
								
								spec-main/fixtures/apps/set-path/package.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								spec-main/fixtures/apps/set-path/package.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| { | ||||
|   "name": "electron-test-set-path", | ||||
|   "main": "main.js" | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Cheng Zhao
				Cheng Zhao