Browse Source

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: [email protected] <[email protected]>
Cheng Zhao 2 years ago
parent
commit
9483e714c4

+ 14 - 4
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()`
 

+ 4 - 1
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;

+ 2 - 0
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")

+ 1 - 1
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<JsonPrefStore> user_pref_store =

+ 4 - 4
shell/browser/electron_browser_client.cc

@@ -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() {

+ 1 - 2
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(

+ 1 - 1
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)

+ 3 - 3
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;

+ 2 - 1
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

+ 49 - 1
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)', () => {

+ 43 - 0
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 = `
+<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 - 0
spec-main/fixtures/apps/set-path/package.json

@@ -0,0 +1,4 @@
+{
+  "name": "electron-test-set-path",
+  "main": "main.js"
+}