Keep browser sign-ins across tabs and restarts (#2089)

* feat(desktop): share persistent browser data across tabs

Keep browser sign-ins and site storage across tabs and app restarts, with a Settings action to clear the shared profile.

* fix(settings): prevent duplicate browser data clears

* fix(desktop): complete browser data cleanup

Clear legacy per-tab profiles after upgrades and localize the browser data settings in every supported language.

* fix(desktop): close browser profile cleanup races
This commit is contained in:
Mohamed Boudra
2026-07-16 00:40:24 +08:00
committed by GitHub
parent 9423b091b9
commit 328361667f
32 changed files with 1413 additions and 148 deletions
@@ -1,3 +1,9 @@
import {
getDesktopHost,
type DesktopAttachedBrowserRegistration,
type DesktopBrowserBridge,
} from "@/desktop/host";
const RESIDENT_BROWSER_HOST_ID = "paseo-browser-resident-webviews";
const BROWSER_ID_ATTRIBUTE = "data-paseo-browser-id";
const RESIDENT_VIEWPORT_WIDTH = 1280;
@@ -8,6 +14,62 @@ const residentWebviewSizesByBrowserId = new Map<string, { width: number; height:
interface BrowserWebviewElement extends HTMLElement {
src: string;
getWebContentsId(): number;
}
interface BrowserWebviewIdentity {
browserId: string;
workspaceId: string;
}
export interface BrowserWebviewProfileHost {
profilePartition: string;
registerAttachedBrowser(input: DesktopAttachedBrowserRegistration): Promise<void>;
}
function isAttachedBrowserBridge(
browser: DesktopBrowserBridge | undefined,
): browser is BrowserWebviewProfileHost {
return (
browser !== undefined &&
typeof browser.profilePartition === "string" &&
browser.profilePartition.startsWith("persist:") &&
typeof browser.registerAttachedBrowser === "function"
);
}
function getBrowserBridge(override?: BrowserWebviewProfileHost): BrowserWebviewProfileHost {
if (override) {
return override;
}
const browser = getDesktopHost()?.browser;
if (!isAttachedBrowserBridge(browser)) {
throw new Error("Electron browser profile bridge is unavailable");
}
return browser;
}
function registerBrowserWhenAttached(
webview: BrowserWebviewElement,
identity: BrowserWebviewIdentity,
browser: BrowserWebviewProfileHost,
): void {
webview.addEventListener(
"did-attach",
() => {
const webContentsId = webview.getWebContentsId();
void browser
.registerAttachedBrowser({
browserId: identity.browserId,
workspaceId: identity.workspaceId,
webContentsId,
})
.catch((error) => {
console.error("[browser-webview] attached registration failed", error);
});
},
{ once: true },
);
}
function trimNonEmpty(value: string | null | undefined): string | null {
@@ -104,21 +166,30 @@ function clearResidentWebviewParkingStyle(webview: HTMLElement): void {
export function prepareBrowserWebview(
webview: HTMLElement,
input: { browserId: string; initialUrl?: string | null },
input: {
browserId: string;
workspaceId: string;
initialUrl?: string | null;
profileHost?: BrowserWebviewProfileHost;
},
): void {
const browser = getBrowserBridge(input.profileHost);
webview.setAttribute(BROWSER_ID_ATTRIBUTE, input.browserId);
webview.setAttribute("partition", `persist:paseo-browser-${input.browserId}`);
webview.setAttribute("partition", browser.profilePartition);
webview.setAttribute("allowpopups", "true");
webview.setAttribute("spellcheck", "false");
webview.setAttribute("autosize", "on");
if (input.initialUrl) {
(webview as BrowserWebviewElement).src = input.initialUrl;
}
registerBrowserWhenAttached(webview as BrowserWebviewElement, input, browser);
}
export function ensureResidentBrowserWebview(input: {
browserId: string;
workspaceId: string;
url: string;
profileHost?: BrowserWebviewProfileHost;
}): HTMLElement | null {
const browserId = trimNonEmpty(input.browserId);
if (!browserId) {
@@ -144,7 +215,12 @@ export function ensureResidentBrowserWebview(input: {
}
const webview = ownerDocument.createElement("webview") as BrowserWebviewElement;
prepareBrowserWebview(webview, { browserId, initialUrl: input.url });
prepareBrowserWebview(webview, {
browserId,
workspaceId: input.workspaceId,
initialUrl: input.url,
profileHost: input.profileHost,
});
releaseResidentBrowserWebview(browserId, webview);
return webview;
}