experiments in a post-browser web
10
fork

Configure Feed

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

refactor: unified popup handler for all window types

Extract openPopupInPageHost() shared function for popup creation.
Register setWindowOpenHandler on non-canvas web pages (modals, overlays,
slides) so JS-opened links always open in proper page host canvas windows
with navbar, widgets, and space mode inheritance.

+169 -138
+169 -138
backend/electron/ipc.ts
··· 2590 2590 // and set up a setWindowOpenHandler that routes popups through Peek's window system 2591 2591 // instead of letting Electron create raw default BrowserWindows. 2592 2592 // 2593 + // Shared helper: open a popup URL in a proper Peek page host canvas window. 2594 + // Used by both canvas webview popups and non-canvas page popups. 2595 + async function openPopupInPageHost(popupUrl: string, openerWinId: number, openerSource: string, openerUrl?: string): Promise<void> { 2596 + // Track the popup URL in history 2597 + let popupItemId: string | undefined; 2598 + try { 2599 + const popupTrack = trackWindowLoad(popupUrl, { 2600 + source: 'window-open', 2601 + sourceId: openerUrl || popupUrl, 2602 + windowType: 'main', 2603 + }); 2604 + popupItemId = popupTrack.itemId; 2605 + if (popupTrack.created) { 2606 + publish('system', PubSubScopes.GLOBAL, 'item:created', { 2607 + itemId: popupTrack.itemId, 2608 + itemType: 'url', 2609 + content: popupUrl, 2610 + windowId: openerWinId 2611 + }); 2612 + } 2613 + } catch (e) { 2614 + DEBUG && console.log('Failed to track webview popup:', e); 2615 + } 2616 + 2617 + // Inherit space mode from parent window if applicable 2618 + let spaceMode: { spaceId: string; spaceName: string; color: string } | undefined = undefined; 2619 + const parentContext = getContextEntry('mode', openerWinId); 2620 + if (parentContext && parentContext.value === 'space' && parentContext.metadata) { 2621 + spaceMode = { 2622 + spaceId: parentContext.metadata.spaceId as string, 2623 + spaceName: parentContext.metadata.spaceName as string, 2624 + color: parentContext.metadata.color as string, 2625 + }; 2626 + } 2627 + 2628 + // Build page container URL (includes openerUrl for the bridge shim) 2629 + const openerWin = BrowserWindow.fromId(openerWinId); 2630 + const parentBounds = openerWin ? openerWin.getBounds() : { x: 100, y: 100, width: 1024, height: 768 }; 2631 + const pageParams = new URLSearchParams({ 2632 + url: popupUrl, 2633 + x: String(parentBounds.x + 30), 2634 + y: String(parentBounds.y + 30), 2635 + width: String(1024), 2636 + height: String(768), 2637 + ...(openerUrl ? { openerUrl } : {}), 2638 + }); 2639 + const loadUrl = `peek://app/page/index.html?${pageParams.toString()}`; 2640 + 2641 + // Determine IZUI session state for transient detection 2642 + const coordinator = getIzuiCoordinator(); 2643 + const isTransient = coordinator.isTransient(); 2644 + coordinator.evaluateOnShow(); 2645 + 2646 + // Use profile-specific session for isolation 2647 + const profileSession = getProfileSession(); 2648 + 2649 + // Create the new BrowserWindow (page container) with fullscreen canvas 2650 + const popupWin = new BrowserWindow({ 2651 + frame: false, 2652 + width: 1024, 2653 + height: 768, 2654 + x: parentBounds.x + 30, 2655 + y: parentBounds.y + 30, 2656 + show: isHeadless() ? false : true, 2657 + transparent: true, 2658 + webPreferences: { 2659 + preload: getPreloadPath(), 2660 + session: profileSession, 2661 + webviewTag: true, 2662 + }, 2663 + }); 2664 + 2665 + // Set up fullscreen transparent canvas for the popup 2666 + if (!isHeadless()) { 2667 + const popupDisplay = screen.getDisplayNearestPoint({ x: parentBounds.x + 30, y: parentBounds.y + 30 }); 2668 + const { width: psw, height: psh } = popupDisplay.workAreaSize; 2669 + const { x: pdx, y: pdy } = popupDisplay.workArea; 2670 + popupWin.setSize(psw, psh); 2671 + popupWin.setPosition(pdx, pdy); 2672 + popupWin.setBackgroundColor('#00000000'); 2673 + } 2674 + 2675 + // Register in window manager with proper IZUI role 2676 + const popupParams: Record<string, unknown> = { 2677 + address: popupUrl, 2678 + transient: isTransient, 2679 + parentWindowId: openerWinId, 2680 + role: 'child-content', 2681 + }; 2682 + if (spaceMode) { 2683 + popupParams.spaceMode = spaceMode; 2684 + } 2685 + registerWindow(popupWin.id, openerSource, popupParams); 2686 + trackWindow(popupWin); 2687 + coordinator.pushWindow(popupWin.id); 2688 + 2689 + // Set mode context (inherit group mode or detect from URL) 2690 + if (spaceMode) { 2691 + addContextEntry('mode', 'space', { 2692 + windowId: popupWin.id, 2693 + source: openerSource, 2694 + metadata: { ...spaceMode, url: popupUrl, inheritedFrom: openerWinId }, 2695 + }); 2696 + } else { 2697 + const detectedMode = detectModeFromUrl(popupUrl); 2698 + addContextEntry('mode', detectedMode, { 2699 + windowId: popupWin.id, 2700 + source: openerSource, 2701 + metadata: { url: popupUrl }, 2702 + }); 2703 + } 2704 + 2705 + // Store popup-to-opener mapping for the postMessage bridge 2706 + popupToOpener.set(popupWin.id, openerWinId); 2707 + popupWin.on('closed', () => { 2708 + popupToOpener.delete(popupWin.id); 2709 + DEBUG && console.log(`[webview-popup] Cleaned up opener mapping for popup ${popupWin.id}`); 2710 + }); 2711 + 2712 + // Load the page container URL 2713 + await popupWin.loadURL(loadUrl); 2714 + 2715 + // Add ESC handler (also sets up did-attach-webview for the new window) 2716 + addEscHandler(popupWin); 2717 + 2718 + // Update dock visibility 2719 + updateDockVisibility(); 2720 + 2721 + console.log(`[webview-popup] Created Peek window ${popupWin.id} for popup: ${popupUrl}`); 2722 + } 2723 + 2593 2724 // Also adds Cmd+L interception on guest webContents for the floating navbar. 2594 2725 // (Keystrokes inside the webview never reach the host's before-input-event.) 2595 2726 // Only canvas pages have a <webview> guest — set up popup/Cmd+L handlers ··· 2659 2790 if (popupUrl.startsWith('http://') || popupUrl.startsWith('https://')) { 2660 2791 console.log(`[webview-popup] Intercepted popup from window ${win.id}: ${popupUrl}`); 2661 2792 2793 + // Get the source and opener URL for the shared handler 2794 + const parentInfo = getWindowInfo(win.id); 2795 + const source = parentInfo ? parentInfo.source : 'system'; 2796 + let openerUrl = url; // fallback to the registered URL 2797 + try { 2798 + openerUrl = guestWebContents.getURL() || url; 2799 + } catch (e) { 2800 + DEBUG && console.log('[webview-popup] Failed to get opener URL:', e); 2801 + } 2802 + 2662 2803 // Open asynchronously — create a proper Peek page container window. 2663 - // This runs outside the synchronous handler return. 2664 2804 (async () => { 2665 2805 try { 2666 - // Track the popup URL in history 2667 - let popupItemId: string | undefined; 2668 - try { 2669 - const popupTrack = trackWindowLoad(popupUrl, { 2670 - source: 'window-open', 2671 - sourceId: url, 2672 - windowType: 'main', 2673 - }); 2674 - popupItemId = popupTrack.itemId; 2675 - if (popupTrack.created) { 2676 - publish('system', PubSubScopes.GLOBAL, 'item:created', { 2677 - itemId: popupTrack.itemId, 2678 - itemType: 'url', 2679 - content: popupUrl, 2680 - windowId: win.id 2681 - }); 2682 - } 2683 - } catch (e) { 2684 - DEBUG && console.log('Failed to track webview popup:', e); 2685 - } 2686 - 2687 - // Inherit space mode from parent window if applicable 2688 - let spaceMode: { spaceId: string; spaceName: string; color: string } | undefined = undefined; 2689 - const parentContext = getContextEntry('mode', win.id); 2690 - if (parentContext && parentContext.value === 'space' && parentContext.metadata) { 2691 - spaceMode = { 2692 - spaceId: parentContext.metadata.spaceId as string, 2693 - spaceName: parentContext.metadata.spaceName as string, 2694 - color: parentContext.metadata.color as string, 2695 - }; 2696 - } 2697 - 2698 - // Get the source address from the parent window's registration 2699 - const parentInfo = getWindowInfo(win.id); 2700 - const source = parentInfo ? parentInfo.source : 'system'; 2701 - 2702 - // Get the opener's current URL for the window.opener shim 2703 - let openerUrl = url; // fallback to the registered URL 2704 - try { 2705 - openerUrl = guestWebContents.getURL() || url; 2706 - } catch (e) { 2707 - DEBUG && console.log('[webview-popup] Failed to get opener URL:', e); 2708 - } 2709 - 2710 - // Build page container URL (includes openerUrl for the bridge shim) 2711 - const parentBounds = win.getBounds(); 2712 - const pageParams = new URLSearchParams({ 2713 - url: popupUrl, 2714 - x: String(parentBounds.x + 30), 2715 - y: String(parentBounds.y + 30), 2716 - width: String(1024), 2717 - height: String(768), 2718 - openerUrl, 2719 - }); 2720 - const loadUrl = `peek://app/page/index.html?${pageParams.toString()}`; 2721 - 2722 - // Determine IZUI session state for transient detection 2723 - const coordinator = getIzuiCoordinator(); 2724 - const isTransient = coordinator.isTransient(); 2725 - coordinator.evaluateOnShow(); 2726 - 2727 - // Use profile-specific session for isolation 2728 - const profileSession = getProfileSession(); 2729 - 2730 - // Create the new BrowserWindow (page container) with fullscreen canvas 2731 - const popupWin = new BrowserWindow({ 2732 - frame: false, 2733 - width: 1024, 2734 - height: 768, 2735 - x: parentBounds.x + 30, 2736 - y: parentBounds.y + 30, 2737 - show: isHeadless() ? false : true, 2738 - transparent: true, 2739 - webPreferences: { 2740 - preload: getPreloadPath(), 2741 - session: profileSession, 2742 - webviewTag: true, 2743 - }, 2744 - }); 2745 - 2746 - // Set up fullscreen transparent canvas for the popup 2747 - if (!isHeadless()) { 2748 - const popupDisplay = screen.getDisplayNearestPoint({ x: parentBounds.x + 30, y: parentBounds.y + 30 }); 2749 - const { width: psw, height: psh } = popupDisplay.workAreaSize; 2750 - const { x: pdx, y: pdy } = popupDisplay.workArea; 2751 - popupWin.setSize(psw, psh); 2752 - popupWin.setPosition(pdx, pdy); 2753 - popupWin.setBackgroundColor('#00000000'); 2754 - } 2755 - 2756 - // Register in window manager with proper IZUI role 2757 - const popupParams: Record<string, unknown> = { 2758 - address: popupUrl, 2759 - transient: isTransient, 2760 - parentWindowId: win.id, 2761 - role: 'child-content', 2762 - }; 2763 - if (spaceMode) { 2764 - popupParams.spaceMode = spaceMode; 2765 - } 2766 - registerWindow(popupWin.id, source, popupParams); 2767 - trackWindow(popupWin); 2768 - coordinator.pushWindow(popupWin.id); 2769 - 2770 - // Set mode context (inherit group mode or detect from URL) 2771 - if (spaceMode) { 2772 - addContextEntry('mode', 'space', { 2773 - windowId: popupWin.id, 2774 - source, 2775 - metadata: { ...spaceMode, url: popupUrl, inheritedFrom: win.id }, 2776 - }); 2777 - } else { 2778 - const detectedMode = detectModeFromUrl(popupUrl); 2779 - addContextEntry('mode', detectedMode, { 2780 - windowId: popupWin.id, 2781 - source, 2782 - metadata: { url: popupUrl }, 2783 - }); 2784 - } 2785 - 2786 - // Store popup-to-opener mapping for the postMessage bridge 2787 - popupToOpener.set(popupWin.id, win.id); 2788 - popupWin.on('closed', () => { 2789 - popupToOpener.delete(popupWin.id); 2790 - DEBUG && console.log(`[webview-popup] Cleaned up opener mapping for popup ${popupWin.id}`); 2791 - }); 2792 - 2793 - // Load the page container URL 2794 - await popupWin.loadURL(loadUrl); 2795 - 2796 - // Add ESC handler (also sets up did-attach-webview for the new window) 2797 - addEscHandler(popupWin); 2798 - 2799 - // Update dock visibility 2800 - updateDockVisibility(); 2801 - 2802 - console.log(`[webview-popup] Created Peek window ${popupWin.id} for popup: ${popupUrl}`); 2806 + await openPopupInPageHost(popupUrl, win.id, source, openerUrl); 2803 2807 } catch (e) { 2804 2808 console.error('[webview-popup] Failed to create popup window:', e); 2805 2809 } ··· 2887 2891 } catch (err) { 2888 2892 console.error('[non-canvas-bg] Failed to check/set background:', err); 2889 2893 } 2894 + }); 2895 + } 2896 + 2897 + // Popup handler for non-canvas web pages (modals, overlays, slides, quick-views). 2898 + // Canvas pages get this via the webview guest's setWindowOpenHandler above. 2899 + // Non-canvas pages load directly in the BrowserWindow, so we intercept popups here. 2900 + if (isWebPage && !useCanvas) { 2901 + win.webContents.setWindowOpenHandler(({ url: popupUrl }) => { 2902 + if (popupUrl.startsWith('http://') || popupUrl.startsWith('https://')) { 2903 + console.log(`[non-canvas-popup] Intercepted popup from window ${win.id}: ${popupUrl}`); 2904 + 2905 + const parentInfo = getWindowInfo(win.id); 2906 + const source = parentInfo ? parentInfo.source : 'system'; 2907 + 2908 + (async () => { 2909 + try { 2910 + await openPopupInPageHost(popupUrl, win.id, source, url); 2911 + } catch (e) { 2912 + DEBUG && console.log('[non-canvas-popup] Failed to open popup in page host:', e); 2913 + } 2914 + })(); 2915 + 2916 + return { action: 'deny' }; 2917 + } 2918 + 2919 + // Allow non-http(s) URLs (about:blank, data:, etc.) to open normally 2920 + return { action: 'allow' }; 2890 2921 }); 2891 2922 } 2892 2923