experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(electron): route v2-tile peek:// webviews through tile-preload

+108 -4
+33 -4
backend/electron/entry.ts
··· 101 101 } from './session-partition.js'; 102 102 import { saveSessionSnapshot, restoreSessionSnapshot, readSessionCrashState, markSessionDirty, startAutosaveTimer, stopAutosaveTimer, updateAutosaveInterval } from './session.js'; 103 103 import { initDisplayWatcher, cleanupDisplayWatcher } from './display-watcher.js'; 104 + import { getTileWebPreferencesForWebviewUrl } from './tile-launcher.js'; 105 + import { getLazyTileManifest } from './tile-lazy.js'; 106 + import { getTilePreloadPath } from './config.js'; 104 107 105 108 // Early diagnostic logging for URL handling (writes to /tmp/ to avoid userData path issues) 106 109 try { ··· 168 171 } 169 172 170 173 // Inject preload into <webview> elements that load peek:// URLs. 171 - // This gives internal widget pages (e.g., HUD widgets) access to window.app API 172 - // while leaving external web content webviews untouched. 174 + // This gives internal widget pages (e.g., HUD widgets, widget-demo cards) 175 + // access to window.app API while leaving external web content webviews untouched. 176 + // 177 + // Two paths (Phase 2.5 #2 — webview preload migration): 178 + // 1. v2 tiles: if the URL resolves to a registered v2 tile manifest, 179 + // use tile-preload.cjs with capability-token argv (the same shape 180 + // v2 BrowserWindows get via webPreferences.additionalArguments). 181 + // Webview children inherit the parent tile's grant via 182 + // `getTileWebPreferencesForWebviewUrl`. 183 + // 2. v1 fallback: any peek:// URL whose tile-id isn't a v2 tile (e.g. 184 + // `peek://ext/hud/widgets/...` while HUD is still v1) keeps the 185 + // legacy preload.js. This will go away when HUD is migrated. 173 186 // 174 187 // Also inject Proton Pass webauthn.js into http/https webviews for passkey support. 175 188 // The script overrides navigator.credentials.create/get in the main world. 176 189 app.on('web-contents-created', (_event, contents) => { 177 190 contents.on('will-attach-webview', (_wvEvent, webPreferences, params) => { 178 191 if (params.src && params.src.startsWith('peek://')) { 179 - webPreferences.preload = preloadPath; 180 - DEBUG && console.log(`[webview] Injecting preload for peek:// webview: ${params.src}`); 192 + const tilePreloadPath = getTilePreloadPath(); 193 + const tileWebPrefs = tilePreloadPath 194 + ? getTileWebPreferencesForWebviewUrl(params.src, tilePreloadPath, getLazyTileManifest) 195 + : null; 196 + if (tileWebPrefs) { 197 + webPreferences.preload = tileWebPrefs.preload; 198 + // Cast: Electron's WebPreferences type for webviews omits 199 + // additionalArguments in some versions, but the runtime accepts 200 + // it (set in BrowserWindow webPrefs the same way). 201 + (webPreferences as { additionalArguments?: string[] }).additionalArguments = 202 + tileWebPrefs.additionalArguments; 203 + DEBUG && console.log( 204 + `[webview] Injecting v2 tile-preload for ${params.src} (tile=${tileWebPrefs.tileId}, entry=${tileWebPrefs.entryId})` 205 + ); 206 + } else { 207 + webPreferences.preload = preloadPath; 208 + DEBUG && console.log(`[webview] Injecting v1 preload for peek:// webview: ${params.src}`); 209 + } 181 210 } 182 211 }); 183 212
+75
backend/electron/tile-launcher.ts
··· 475 475 } 476 476 477 477 /** 478 + * Resolve a peek:// URL to v2 tile web preferences for a `<webview>` child. 479 + * 480 + * Like `getTileWebPreferencesForUrl`, but treats child URLs that don't 481 + * correspond to a declared tile entry as belonging to the parent tile's 482 + * grant. This is the loader path for HTML assets a tile loads inside a 483 + * `<webview>` element (e.g. `widget-demo`'s widget cards loading 484 + * `peek://ext/widget-demo/widgets/clock.html` — `widgets/clock.html` is 485 + * not declared as its own tile entry, but logically belongs to 486 + * `widget-demo`). 487 + * 488 + * Resolution rules: 489 + * - URL must be peek:// 490 + * - Tile-id (URL host or first path segment under `peek://ext/`) must 491 + * match a registered v2 manifest 492 + * - If a tile entry's `url` matches the path, mint a token for that 493 + * specific entry (same as `getTileWebPreferencesForUrl`) 494 + * - Otherwise, fall back to the manifest's first tile entry — webview 495 + * children inherit the parent tile's full capability grant 496 + * 497 + * Returns null when the URL isn't peek:// or the tile-id isn't a v2 498 + * tile. Callers should fall back to the legacy preload in that case 499 + * (e.g. for v1 extensions like HUD). 500 + */ 501 + export function getTileWebPreferencesForWebviewUrl( 502 + url: string, 503 + tilePreloadPath: string, 504 + lookupLazyManifest: (tileId: string) => TileManifestV2 | null, 505 + ): { preload: string; additionalArguments: string[]; tileId: string; entryId: string } | null { 506 + let host: string; 507 + let pathPart: string; 508 + try { 509 + const parsed = new URL(url); 510 + if (parsed.protocol !== 'peek:') return null; 511 + const cleanPath = parsed.pathname.replace(/^\//, ''); 512 + if (parsed.host === 'ext') { 513 + const segments = cleanPath.split('/'); 514 + if (segments.length < 2) return null; 515 + host = segments[0]; 516 + pathPart = segments.slice(1).join('/'); 517 + } else { 518 + host = parsed.host; 519 + pathPart = cleanPath; 520 + } 521 + } catch { 522 + return null; 523 + } 524 + 525 + const manifest = getTileManifest(host) || lookupLazyManifest(host); 526 + if (!manifest) return null; 527 + if (!manifest.tiles || manifest.tiles.length === 0) return null; 528 + 529 + // Prefer an exact entry match, fall back to the first entry for 530 + // webview children that aren't declared as standalone tile entries. 531 + const entry = manifest.tiles.find(t => t.url === pathPart) || manifest.tiles[0]; 532 + 533 + const grant = resolveCapabilities( 534 + manifest.id, 535 + manifest.capabilities, 536 + manifest.builtin === true, 537 + ); 538 + const token = generateToken(manifest.id, entry.id, grant); 539 + 540 + return { 541 + preload: tilePreloadPath, 542 + additionalArguments: [ 543 + `--tile-id=${manifest.id}`, 544 + `--tile-entry=${entry.id}`, 545 + `--tile-token=${token}`, 546 + ], 547 + tileId: manifest.id, 548 + entryId: entry.id, 549 + }; 550 + } 551 + 552 + /** 478 553 * Send the `tile:shutdown` IPC to a single tile window, wait a short grace 479 554 * period for its `api.onShutdown()` callbacks to run, and then close the 480 555 * BrowserWindow.