Skip to content

feat(app): embedded browser tab (WebContentsView) - #1204

Merged
Astro-Han merged 8 commits into
devfrom
claude/browser-tab
Jun 8, 2026
Merged

feat(app): embedded browser tab (WebContentsView)#1204
Astro-Han merged 8 commits into
devfrom
claude/browser-tab

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a desktop-only Browser tab to the session right panel: one app-owned
WebContentsView on a persistent partition, reusing Electron's own Chromium.
It comes with a navigation toolbar (back / forward / reload-or-stop, an editable
two-tone address bar, and an overflow menu for open in system browser / copy
link
/ clear browsing data) and the prototype's four states (empty, loading,
loaded, clear-data confirm).

The page lives in the main process and survives tab switches; the renderer
owns the DOM chrome and reports the content rect each animation frame while
visible, so main sizes/shows the native overlay and hides it on tab switch,
panel collapse, or whenever an app modal / the tab's own menu would paint over
it (a native view always paints above the DOM).

The tab is opt-in (opened via browser.toggle / the panel + menu, closable —
mirroring Review) and gated on the Electron platform via canUseBrowser(platform),
so it never appears on web.

Why

First shippable slice of #1186 (embedded browser). Landing the user-driven
browser surface now keeps the follow-up automation PR focused purely on the
narrow CDP bridge. Design was approved from docs/design/preview/browser-tab-prototype.html.

Related Issue

Part of #1186. Automation (a narrow loopback CDP bridge) and HTML-artifact
preview are deliberately out of scope for this PR.

Human Review Status

Pending

Review Focus

  • Native-overlay lifecyclebrowser-panel.tsx (rAF-while-visible rect
    reporting + shouldShow suppression) and main/browser/controller.ts
    (per-window WebContentsView, show/hide, bounds via the window zoom factor).
    Does the view stay glued to the content region across resize / panel
    drag / open-close, and is it reliably hidden behind menus and modals?
  • Security of the embedded viewmain/browser/options.ts (own partition,
    no preload, sandbox, context isolation) + controller.ts (deny all permission
    requests, never spawn child windows) + logic.ts: parseNavigable makes only
    http/https loadable in-place (file://, javascript: rejected), and
    safeExternalUrl hands page-provided links to the OS only for a tight
    allow-list (mailto:/tel:) — file://, javascript:, and custom app protocols
    are dropped so a hostile page can't launch local files or registered handlers.
  • Platform gatingcanUseBrowser(platform) filters the chip, the +
    menu, the command, and the tab body off web.

Risk Notes

  • Interactive WebContentsView QA is pending a manual run. The overlay is a
    native layer outside the DOM, so headless/unit tests can't assert its on-screen
    position, navigation, clear-data, or dark-mode rendering. Logic, types, and
    bundling are fully covered (below); the live overlay behavior should be
    eyeballed against the prototype before merge. The conditional UI checklist item
    is therefore left unticked.
  • New persistent local storage. The view uses a persist:pawwork-browser
    partition (cookies/cache on disk so logins survive restart). Clear browsing
    data
    wipes the partition session directly (clearStorageData() +
    clearCache()), so it works even before any view exists (tab opened fresh
    after restart); live views then reload to show the signed-out state. No
    multi-profile.
  • New web surface (defense-in-depth). The view renders arbitrary external
    sites; it is locked down as described in Review Focus and shares no session,
    cookies, or IPC bridge with the app renderer.
  • Platform: macOS + Windows both attach via BrowserWindow.contentView; the
    overlay sits below the custom titlebar so it never covers the macOS traffic
    lights. No packaging/updater/signing surface touched.
  • No new dependencies. Reuses Electron's bundled Chromium.
  • rAF-while-visible runs a per-frame getBoundingClientRect while the browser
    tab is the visible active tab (stops on switch-away); IPC is diffed so it only
    fires on a real device-pixel change. ResizeObserver can't see position-only
    moves, hence rAF.

How To Verify

turbo typecheck (all 8 packages): pass
eslint (all changed src files): pass
unit — desktop-electron: 477 passed / 0 failed (incl. browser logic + view-security + external-scheme allow-list)
unit — app: 1836 passed / 0 failed (incl. address normalization, view-state, command palette)
electron-vite build: main / preload / renderer all bundle; the browser IPC
  channels are present in out/main/index.js and out/preload/index.js
Interactive overlay QA (positioning, navigate, back/forward, reload/stop,
  clear-data, dark mode): PENDING manual run — see Risk Notes

Screenshots or Recordings

Design reference (approved): docs/design/preview/browser-tab-prototype.html
(loaded / empty / loading / clear-data / dark variants). A live capture will be
attached after the manual overlay QA noted above.

Checklist

  • Type label — this PR carries exactly one of bug, enhancement, task, documentation. Type labels are author-added; the labeler bot does NOT assign them. Add the label in the GitHub UI, then tick this. (enhancement)
  • Routing labels — this PR carries at least one of app, ui, platform, harness, ci. The labeler bot assigns these on PR open based on changed paths. Confirm the bot's choice (or override if wrong), then tick this. (app, platform, and ui — the latter via the labeler's **/*.tsx rule; all correct.)
  • Priority label — this PR carries exactly one of P0, P1, P2, P3. The priority-triage bot suggests one on PR open. Confirm or override, then tick this. (P2 — opt-in desktop feature, no urgency.)
  • Human Review Status above is set to Pending, Approved by @<reviewer>, or Not required: <reason> (default is Pending; "not required" is restricted to bot-authored low-risk PRs).
  • I linked the related issue, or stated in Summary why there is no issue.
  • I described the review focus and any meaningful risks.
  • I replaced the example block in How To Verify with the real verification steps and the key result for each.
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope.
  • (conditional) I manually checked visible UI or copy changes when needed, with screenshots or recordings. Leave unticked only if no visible UI or copy changed.
  • (conditional) I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes. Leave unticked only if no platform/packaging surface was touched.
  • (conditional) I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant. Leave unticked only if none of those surfaces was touched.
  • I reviewed the final diff for unrelated changes and suspicious dependency changes.
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English.

Summary by CodeRabbit

  • New Features

    • Embedded browser panel in the right sidebar with navigation controls, address bar (normalization/formatting), and browsing-data clearing.
    • Platform-aware desktop integration exposing an embedded-browser capability and a quick-access browser.toggle command.
  • Tests

    • Added unit tests for URL handling, view visibility/rect logic, controller logic, options, and integration wiring.
  • Internationalization

    • Added English and Chinese translations for browser UI text and commands.

Add a desktop-only "Browser" tab to the session right panel: one app-owned
WebContentsView on a persistent partition, driven by a navigation toolbar
(back / forward / reload-or-stop, an editable two-tone address bar, and an
overflow menu for open-in-system-browser / copy-link / clear-data) plus the
prototype's empty, loading, loaded, and clear-data-confirm states.

The page lives in the main process and survives tab switches; the renderer
owns the DOM chrome and reports the content rect each animation frame while
visible, so main sizes/shows the native overlay and hides it on tab switch,
panel collapse, or when an app modal / the tab's own menu would paint over it.

Security: the view loads only http/https (validated in main), runs on its own
partition with no preload, sandboxed and context-isolated, denies all
permission requests, and hands non-web schemes to the system browser. The
feature is gated on the Electron platform via canUseBrowser(platform) and is
absent on web.

Part of #1186. Automation (a narrow CDP bridge) and HTML-artifact preview are
deliberately out of scope for this PR.
@Astro-Han Astro-Han added the enhancement New feature or request label Jun 7, 2026
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2969f8ef-f3c3-4052-9245-92ce99d1379d

📥 Commits

Reviewing files that changed from the base of the PR and between 61aeff1 and 12d15cb.

📒 Files selected for processing (11)
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/session/browser/browser-panel.tsx
  • packages/app/src/pages/session/browser/url.test.ts
  • packages/app/src/pages/session/browser/url.ts
  • packages/app/src/pages/session/right-panel-tab-strip.tsx
  • packages/app/src/pages/session/session-side-panel.test.tsx
  • packages/app/src/pages/session/session-side-panel.tsx
  • packages/desktop-electron/src/main/browser/controller.ts
  • packages/desktop-electron/src/main/browser/logic.test.ts
  • packages/desktop-electron/src/main/browser/logic.ts

📝 Walkthrough

Walkthrough

Adds a desktop-only embedded browser: platform contracts, Electron controller and options, IPC + preload bridge, renderer BrowserPanel UI, URL/view helpers and tests, right-panel tab integration, command palette inclusion, and i18n strings.

Changes

Embedded Browser Integration

Layer / File(s) Summary
Platform API contracts and type definitions
packages/app/src/context/platform.tsx, packages/app/src/desktop-api.ts
Defines BrowserBridge, BrowserState, BrowserViewLayout, BrowserViewRect; extends Platform with optional browser; adds canUseBrowser and re-exports types.
Desktop Electron controller, options, and logic
packages/desktop-electron/src/main/browser/options.ts, packages/desktop-electron/src/main/browser/logic.ts, packages/desktop-electron/src/main/browser/controller.ts, packages/desktop-electron/src/main/browser/*.test.ts
Implements BrowserViewController, BROWSER_STATE_CHANNEL, secure browserViewWebPreferences() and BROWSER_PARTITION, URL/bounds/state helpers (parseNavigable, safeExternalUrl, computeViewBounds, deriveBrowserState), and related tests.
IPC handlers, preload bridge, and renderer plumbing
packages/desktop-electron/src/main/ipc/browser.ts, packages/desktop-electron/src/preload/index.ts, packages/desktop-electron/src/preload/types.ts, packages/desktop-electron/src/renderer/index.tsx, packages/desktop-electron/src/main/index.ts
Registers browser IPC, manages per-window controller lifecycle, exposes browser API on preload with navigation/state/subscription methods, updates preload types, and wires window.api.browser into createPlatform().
URL/address and view-state utilities + tests
packages/app/src/pages/session/browser/url.ts, packages/app/src/pages/session/browser/view-state.ts, packages/app/src/pages/session/browser/*.test.ts
Adds normalizeAddressInput and formatAddress for the address bar, shouldShowBrowserView and rectsEqual for overlay visibility and rect tolerance, with unit tests.
BrowserPanel component
packages/app/src/pages/session/browser/browser-panel.tsx
SolidJS BrowserPanel with state subscription/initial seed, animation-frame rect measurement, atomic setView updates, navigation toolbar, editable address bar, overflow menu, clear-data confirmation, and native-view host rendering.
Right-panel tab, icon, and session integration
packages/app/src/pages/session/right-panel-tabs.ts, packages/app/src/pages/session/right-panel-tab-strip.tsx, packages/app/src/pages/session/session-side-panel.tsx
Adds static browser right-panel tab, icon rendering support, i18n label key, tab metadata (command + closable), session-side-panel gating using canUseBrowser, tab filtering, and conditional mounting of BrowserPanel.
Commands, command-palette, and i18n
packages/app/src/pages/session/use-session-commands.tsx, packages/app/src/components/command-palette/*, packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts
Conditionally registers browser.toggle command (mod+shift+b) when desktop/browser available; updates default/search command lists to include browser.toggle; adds English and Chinese localization keys for the browser UI.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Astro-Han/pawwork#619: Both PRs change session-side-panel.tsx’s right-panel content mounting logic and may intersect at the same render boundary.
  • Astro-Han/pawwork#901: Modifies the right-panel tab system and metadata; likely overlaps with RIGHT_PANEL_TAB_VALUES/RIGHT_PANEL_TAB_META changes.
  • Astro-Han/pawwork#1201: Adds related icon assets and touches the same right-panel/tab UI surfaces used by this change.

Suggested labels

desktop

Poem

🐰 A browser hops into the right panel’s light,
Frames measure pixels through day and night,
A bridge hums messages, state flows true,
Sandbox snug, commands bound to a key combo too,
The little rabbit copies links for you.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(app): embedded browser tab (WebContentsView)' clearly and concisely summarizes the main feature addition—a new embedded browser tab using Electron's WebContentsView on the desktop platform.
Description check ✅ Passed The PR description comprehensively addresses all required sections: summary of changes, rationale (issue #1186), human review status, review focus with security details, risk notes including pending QA and new persistent storage, verification steps with test results, and a complete checklist confirming labels, scoping, and platform considerations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/browser-tab

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added app Application behavior and product flows ui Design system and user interface platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions P2 Medium priority labels Jun 7, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested priority: P2 (includes user-path files (packages/app/src/components/command-palette/command-palette-default-items.test.ts, packages/app/src/components/command-palette/command-palette-default-items.ts, packages/app/src/components/command-palette/command-palette-search-items.ts, packages/app/src/context/platform.tsx, packages/app/src/desktop-api.ts, packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts, packages/app/src/pages/session/browser/browser-panel.tsx, packages/app/src/pages/session/browser/url.test.ts, packages/app/src/pages/session/browser/url.ts, packages/app/src/pages/session/browser/view-state.test.ts, packages/app/src/pages/session/browser/view-state.ts, packages/app/src/pages/session/right-panel-tab-strip.tsx, packages/app/src/pages/session/right-panel-tabs.ts, packages/app/src/pages/session/session-side-panel.tsx, packages/app/src/pages/session/use-session-commands.tsx, packages/desktop-electron/src/main/browser/controller.ts, packages/desktop-electron/src/main/browser/logic.test.ts, packages/desktop-electron/src/main/browser/logic.ts, packages/desktop-electron/src/main/browser/options.test.ts, packages/desktop-electron/src/main/browser/options.ts, packages/desktop-electron/src/main/index.ts, packages/desktop-electron/src/main/ipc/browser.ts, packages/desktop-electron/src/preload/index.ts, packages/desktop-electron/src/preload/types.ts, packages/desktop-electron/src/renderer/index.tsx)).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an embedded browser feature for the desktop application, utilizing Electron's WebContentsView to overlay a secure, sandboxed browser panel. It includes the frontend UI components, URL normalization utilities, and the necessary IPC wiring between the main and renderer processes. Feedback on the implementation highlights two critical issues in the main process controller: a security vulnerability in openExternal that could allow dangerous URL schemes to execute on the host system, and a runtime crash risk in the destroy method caused by calling a non-existent close method on WebContents.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/desktop-electron/src/main/browser/controller.ts
Comment thread packages/desktop-electron/src/main/browser/controller.ts
…ling

Codex review (#1204):
- [P1] openExternal now hands only an allow-list (mailto:/tel:) to the OS via
  the new safeExternalUrl helper; file:, javascript:, and custom app protocols
  from page-provided links/window.open are dropped so a hostile page can't
  launch local files or registered handlers.
- [P2] clear-data wipes the persistent partition session directly instead of
  needing a live controller, so it works when the tab is opened fresh after a
  restart; any live views then reload to reflect the signed-out state.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/app/src/pages/session/session-side-panel.tsx`:
- Around line 140-145: The active tab state may remain "browser" even when the
computed shellTabs (from createMemo using view().sidePanel.openTabs() and
canUseBrowser(platform)) filters that value out, leaving the panel blank; after
computing shellTabs ensure the persisted active tab (sidePanelTab()) is
normalized—if sidePanelTab() === "browser" and !canUseBrowser(platform) (or if
sidePanelTab() is not present in shellTabs) call the setter (setSidePanelTab or
equivalent) to pick a valid fallback (e.g., shellTabs[0] or a default like
"search"); update the logic near createMemo(shellTabs) and wherever sidePanelTab
is read (the getter used at line ~368) so the mounted Tabs.Content values always
match the active tab.

In `@packages/desktop-electron/src/main/browser/controller.ts`:
- Around line 74-76: The openExternal method currently uses NON_WEB_SCHEME to
forward many non-about: schemes to shell.openExternal; change this to parse the
url (e.g., with the URL constructor or equivalent) and compare its protocol
against a strict allowlist of exact protocols you intend to support (reference
openExternal and NON_WEB_SCHEME to find the call site), explicitly permit
"about:" handling if needed, and only call shell.openExternal(url).catch(...)
when the parsed protocol is in that allowlist; also ensure malformed URLs are
rejected safely and logged rather than forwarded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c9f7df4-4feb-4160-ab08-6532af3317cb

📥 Commits

Reviewing files that changed from the base of the PR and between 0a832ca and 8d5b57f.

📒 Files selected for processing (26)
  • packages/app/src/components/command-palette/command-palette-default-items.test.ts
  • packages/app/src/components/command-palette/command-palette-default-items.ts
  • packages/app/src/components/command-palette/command-palette-search-items.ts
  • packages/app/src/context/platform.tsx
  • packages/app/src/desktop-api.ts
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/session/browser/browser-panel.tsx
  • packages/app/src/pages/session/browser/url.test.ts
  • packages/app/src/pages/session/browser/url.ts
  • packages/app/src/pages/session/browser/view-state.test.ts
  • packages/app/src/pages/session/browser/view-state.ts
  • packages/app/src/pages/session/right-panel-tab-strip.tsx
  • packages/app/src/pages/session/right-panel-tabs.ts
  • packages/app/src/pages/session/session-side-panel.tsx
  • packages/app/src/pages/session/use-session-commands.tsx
  • packages/desktop-electron/src/main/browser/controller.ts
  • packages/desktop-electron/src/main/browser/logic.test.ts
  • packages/desktop-electron/src/main/browser/logic.ts
  • packages/desktop-electron/src/main/browser/options.test.ts
  • packages/desktop-electron/src/main/browser/options.ts
  • packages/desktop-electron/src/main/index.ts
  • packages/desktop-electron/src/main/ipc/browser.ts
  • packages/desktop-electron/src/preload/index.ts
  • packages/desktop-electron/src/preload/types.ts
  • packages/desktop-electron/src/renderer/index.tsx

Comment thread packages/app/src/pages/session/session-side-panel.tsx
Comment thread packages/desktop-electron/src/main/browser/controller.ts
CodeRabbit (#1204): a session persisted with the browser tab active can be
restored where canUseBrowser is false (the feature flag-disabled or rolled
back). Its chip was already filtered out, but the active "browser" selection
left the panel on a tab whose <Tabs.Content> never mounts (blank body). Mirror
the existing dangling-terminal guard: close the unsupported persisted tab so the
selection falls back to a real one.
…vers

The native WebContentsView is a window-level layer that DOM CSS can't cover. A settings/automations/skills takeover keeps the session mounted (CSS-hidden via `invisible`, not unmounted, to preserve its state), so the overlay kept painting through the takeover. Feed `mainSurfaceOpen` into the visibility gate as a first-class `coveredBySurface` input so the overlay hides whenever a surface covers the session.

Found during manual QA of the browser tab; deeper surface-architecture rework tracked in #1209.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/desktop-electron/src/main/browser/controller.ts`:
- Around line 142-146: reloadIfLoaded() currently only reloads when
state().hasPage is true, missing the case where the first navigation is
in-flight; update reloadIfLoaded() to treat webContents that are loading
(wc.isLoading()) as a live view: if wc.isLoading(), add a one-shot listener
(e.g., 'did-stop-loading' or equivalent) that calls wc.reload() once the
navigation settles (ensure listener is removed and check
this.destroyed/wc.isDestroyed() before reloading), else if state().hasPage then
call wc.reload() immediately; keep existing early return guards (this.destroyed
|| this.wc.isDestroyed()) and reference reloadIfLoaded(), state().hasPage,
wc.isLoading(), wc.reload(), and wc.isDestroyed() when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b93bbd10-d3ba-4e15-9f6f-dfb007da7b69

📥 Commits

Reviewing files that changed from the base of the PR and between 8d5b57f and 61aeff1.

📒 Files selected for processing (8)
  • packages/app/src/pages/session/browser/browser-panel.tsx
  • packages/app/src/pages/session/browser/view-state.test.ts
  • packages/app/src/pages/session/browser/view-state.ts
  • packages/app/src/pages/session/session-side-panel.tsx
  • packages/desktop-electron/src/main/browser/controller.ts
  • packages/desktop-electron/src/main/browser/logic.test.ts
  • packages/desktop-electron/src/main/browser/logic.ts
  • packages/desktop-electron/src/main/ipc/browser.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/desktop-electron/src/main/browser/logic.test.ts
  • packages/desktop-electron/src/main/browser/logic.ts
  • packages/app/src/pages/session/browser/view-state.ts
  • packages/app/src/pages/session/session-side-panel.tsx
  • packages/app/src/pages/session/browser/view-state.test.ts
  • packages/desktop-electron/src/main/ipc/browser.ts
  • packages/app/src/pages/session/browser/browser-panel.tsx

Comment thread packages/desktop-electron/src/main/browser/controller.ts Outdated
Astro-Han added 4 commits June 8, 2026 15:44
…state

PR1's embedded browser is human-driven only; agent-driven navigation is PR2. The empty-state copy promised an ability that does not exist yet and misled users into asking the agent to open pages. Updated zh and en.
The titlebar add-tab "+" menu opens downward over the embedded browser's
content region. The browser paints a native WebContentsView there, and a
native layer ignores DOM stacking, so the menu's lower items were occluded
by the loaded page once a tab was active.

Lift the menu's open state into SessionSidePanel and thread it to BrowserPanel
so the native overlay is suppressed while the menu is up, mirroring how the
browser's own overflow menu and app modals already suppress it.
A bare address with no scheme always got https, so an HTTP-only local dev
server typed as "localhost:3000" failed to load on first navigation.

Pick the scheme from the host: loopback hosts (localhost, *.localhost,
127.0.0.0/8, ::1, 0.0.0.0) default to http since they only ever route to the
local machine; everything else — including private LAN IPs, whose network may
be hostile — keeps the https default, and the user can type http:// to opt in.
reloadIfLoaded() only reloaded when hasPage was true, but hasPage stays false
until the first navigation commits. If browser:clear-data ran while that first
load was in flight, the request already sent with pre-clear cookies could land
as stale signed-in content and never get the reload this path promises.

Treat an in-flight load as live: reload now when a page is loaded, otherwise
defer one reload until did-stop-loading when the first navigation is still in
flight. The decision is extracted as the pure clearDataReloadAction for tests.
@Astro-Han

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Astro-Han
Astro-Han merged commit 168da7a into dev Jun 8, 2026
44 of 50 checks passed
@Astro-Han
Astro-Han deleted the claude/browser-tab branch June 8, 2026 09:09
Astro-Han added a commit that referenced this pull request Jun 10, 2026
First of three flat PRs for embedded-browser agent control (#1186): the self-owned, security-sensitive layer. PawWork's agent needs to drive the embedded browser (PR #1204 shipped only the visible WebContentsView panel) with no Chrome, no extension, no second process; the transport is webContents.debugger <-> ws <-> CDP client. Landing the sealed boundary on its own kept the contract under undiluted review. PR2 (BrowserSession + generic tools) and PR3 (opencli adapters) build on top.

Change boundary (packages/desktop-electron only):
- cdp-bridge.ts: CdpBridge relays ws CDP JSON-RPC to/from webContents.debugger over ws://127.0.0.1:<random>/<secret>. Security contract, each rule test-covered: loopback + OS-random port; constant-time secret match at the HTTP upgrade (wrong secret never consumes the single connection slot; pathname-only compare so query strings are ignored); Host pinned to the loopback authority + any browser Origin rejected; single connection; secret in main-process memory only (redactedEndpoint is the only loggable form); only the passed WebContents attached, never a global --remote-debugging-port; endpoint/secret returned as a same-process value, never over renderer IPC/preload.
- controller-automation.ts: shared main-process registry of per-window controllers; attachForWindow returns the sealed endpoint same-process (consumed by PR2).
- controller.ts: attachAutomation/detachAutomation; destroy() tears the bridge down.
- ipc/browser.ts: lifts the controllers map out of the registerBrowserIpc() closure into the registry; no new renderer channel.
- options.ts: CDP_BRIDGE_SECRET_LENGTH, BRIDGE_START_TIMEOUT_MS. Adds ws + @types/ws (pinned to the already-resolved transitives).
- scripts/smoke-cdp-bridge-ws.ts + cdp-bridge-ws-harness.ts: real-Node + real-ws smoke chained into test:ci.

Review trail (four independent passes; every finding verified before fixing, each fix red/green tested):
- Codex: P1 teardown did not fail in-flight CDP commands (client hung ~30s) -> pending tracking + CDP error frames on stop, secret rotation per start.
- Fresh-eye model review: P1 missing ws socket 'error' handler let one malformed client frame kill the whole main process (reproduced under real Node + ws; invisible to bun test, whose "ws" resolves to Bun's shim) -> terminate on error. P1 bridge-lifetime pending Set let a reconnecting client reusing command ids receive the dead connection's stale result -> pending keyed by issuing connection, swept on close. P2 concurrent start() misreported target-busy -> memoized in-flight start.
- Human review: P2 teardown error frames now carry sessionId so flattened-session clients can route them; P3 pathname-based authorization.
- Gemini: high win.id read inside the 'closed' handler throws after destroy (pre-existing, lifted verbatim) -> id captured up front; medium WebContents destroyed directly is not guaranteed to emit debugger 'detach' -> 'destroyed' listener routes through the same teardown.
- Final self-audit: stop() racing a start() in flight waited out the full listen timeout or surfaced a bare TypeError -> stop() aborts a pending listen with a typed error (target-destroyed/target-busy).

Verification: 34 bun tests under packages/desktop-electron/src/main/browser (20 on the bridge), typecheck (tsgo -b) clean, eslint clean, and smoke:cdp-bridge-ws under real Node + ws@8.20.0 (red-checked: removing the error handler fails the smoke). Full CI green on 7a7de1d including unit-windows-desktop (first Windows run of the smoke) and desktop-smoke (one earlier flake on be50caa was the known report-problem evaluate race, passed on re-run).

Residual risk / deferred: no live dev:desktop attach in this PR by design - the bridge has no caller until PR2's BrowserSession lands; the live attach + CDP round-trip is verified there. The bun suite still runs against Bun's ws shim for frame-level semantics; the dedicated smoke covers the real-ws path.

Closes #1186? No - #1186 stays open for PR2/PR3.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows enhancement New feature or request P2 Medium priority platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant