fix(webview): keep native pages visible under overlays - #954
Conversation
Pre-commit hook ran. Total eslint: 0, total circular: 0
Pre-commit hook ran. Total eslint: 0, total circular: 0
Pre-commit hook ran. Total eslint: 0, total circular: 0
Pre-commit hook ran. Total eslint: 0, total circular: 0
Pre-commit hook ran. Total eslint: 0, total circular: 0
Pre-commit hook ran. Total eslint: 0, total circular: 0
Pre-commit hook ran. Total eslint: 0, total circular: 0
|
Updated the modal solution after visual feedback: full-screen wrappers are no longer treated as opaque holes. The live native page now receives a theme-matched black CALayer scrim, while only the opaque dialog panel is masked out. Marked the PR draft until the revised native composition has screenshot-level manual verification. |
Separate WebView mask cuts from dim-layer holes so modals, spotlight tours, and dropdowns behave like normal React components over inline browsers without bright bleed-through, white padding artifacts, or premature hole registration.
Resolves the EditorStatusBar conflict: develop removed the in-editor LSP dropdown (archived with the rest of the LSP UI), so the overlay-layer registration this branch added for that dropdown no longer has a target. Took develop's version of the file.
`sanitize_occlusion_rects` is called through its `super::` path inside the macOS module, so the unqualified import was dead and tripped the unused-import warning.
The occlusion gate added for dropdowns required `dropdownPosition`, but that state is only ever populated on the portal branch — in-flow panels are placed by their Tailwind position classes and leave it null for their whole lifetime. Every in-flow dropdown therefore skipped overlay registration: on macOS it got no compositor mask, and on Windows and Linux it never tripped the offscreen fallback, so the native page painted over the menu on both. Gate on measured coordinates only where they exist.
Review notes — parking this for nowFirst, credit where it is due: this is an excellent piece of work. Correctly identifying that native child WKWebViews do not participate in the DOM stacking context — and that no amount of That said, I want to park this for a while rather than land it now. This is not a rejection — the approach is right and I want it eventually. It is about the risk profile. The risk1. Correctness moves from one boolean to N registration sites. The old offscreen fallback was blunt but structurally safe: This is not hypothetical. 2. 3. Silent truncation. 4. A real power regression. The old path shrank the surface to 1×1, which effectively stopped the page painting. The new path keeps it fully composited underneath. A modal left open over a page running video or animation now costs materially more GPU/CPU than before. Worth measuring before we commit to it. 5. Platform divergence. This is macOS-only. Windows and Linux keep the offscreen fallback, so every symptom in the description remains on those platforms. It also changes cross-platform behavior asymmetrically: the four overlay atoms ( 6. The one bug class that escapes has no test. Existing coverage tests rect math and specific components. Nothing asserts that a given overlay is registered at all — which is precisely how #1 slipped through. Why park rather than push throughThe blast radius spans input routing, the macOS compositor, and cross-platform behavior, and the verification that would actually de-risk it is manual macOS QA that we do not have bandwidth to do properly right now. The remaining manual checks are still unticked and there is no visual evidence attached, which is exactly the evidence that matters for compositor-level changes. Nothing here is urgent enough to justify absorbing that risk on a partial verification pass. What is already done on the branch
So the branch is in good shape and merges cleanly. It is parked on risk appetite and verification bandwidth, not on code quality. To pick this back up
Moving to Draft to reflect that. Really good work — this is worth finishing properly rather than rushing. |
Problem
Inline Browser WebViews are native child WKWebViews. They do not participate in DOM stacking contexts, so CSS
z-index, portal roots, andoverflow: hiddencannot place React UI above or clip them. The previous mitigation was to send the surface to back or hide the whole WebView whenever an overlay opened, which blanked the live page for any modal, dropdown, tooltip, or tour step raised over a browser pane.The visible symptoms this fixes:
(0,0)or an oversized cutout, because the dropdown registered a native hole before it had been positioned.Solution
Keep the WebView painted and occlude only where React UI actually covers it, on the macOS compositor:
.liquid-modal-content, so the wrapper's padding slack no longer reads as opaque and no white strip appears. A named blackCALayerabove the native page supplies the scrim a DOM element cannot alpha-composite over a sibling WKWebView.useSpotlightTourNativeOcclusion(new) drives layout/code-editor tours and guide highlights: uniform browser dim, popover mask-only, no native highlight holes that would reveal bright page content.(0,0)white block and oversized cutouts. The gate applies only to the portal branch: in-flow panels are placed by Tailwind position classes and never populatedropdownPosition, so gating them on it would have skipped their registration entirely.isActive(page lifecycle) stays separate fromisVisible(native surface): opening an overlay never destroys, reloads, or navigates the page, so cookies, login state, history, scroll, and in-page memory survive. Rects are coalesced and capped at 64 on both sides, and surface commands are serialized per WebView so the last requested visibility wins.Architecture notes live in
docs/workstation/native-webview-scale-system--0622.mdunder Native surface visibility and overlays.Platform support
The compositor path is macOS-only.
set_inline_webview_occlusionsis a no-op under#[cfg(not(target_os = "macos"))], anduseInlineWebviewOcclusionsgates every publish onisMacOS().Windows and Linux keep the pre-existing offscreen fallback:
webviewOverlayBlockedAtommoves the whole surface offscreen while any overlay is registered, so the page blanks rather than staying painted. It is not destroyed — theisActive/isVisiblesplit means cookies, login state, history, scroll, and in-page memory still survive an overlay there. This PR does not regress those platforms; it does not improve them either. Because both paths read the sameuseOverlayLayerregistry, a future WebView2 region-clip or WebKitGTK implementation only has to supply the native layer.Merge note:
developwas merged in twice. The only conflict wassrc/modules/WorkStation/shared/StatusBar/EditorStatusBar.tsx:developremoved the in-editor LSP dropdown entirely, so this branch's overlay-layer registration for that dropdown had no remaining target and was dropped in favor ofdevelop's version of the file.Potential risks
CALayer/CAShapeLayer. Windows and Linux keep the offscreen fallback and are unaffected — but also ungained, so the reported symptoms are only fixed on macOS. The four overlay atoms that previously forced the fallback on every platform (componentIssueModalOpenAtom,quitConfirmationModalOpenAtom,toolbarDropdownOpenAtom,spotlightOpenAtom) now do so only off macOS; a mask-registration gap on macOS would surface as a page painting over that UI rather than as a blank surface.src-tauri/src/commands/handler_list.inc; frontend and Rust ship in the same bundle, so there is no version-skew path, but a partial revert of one side would break the other.Verification
Automated, run on the post-merge tree at
8cd6bd36f:pnpm typecheck(tsc --noEmit --pretty false, whole project) — exit 0, 0 errorspnpm exec vitest run(full suite) — 1236 files, 9796 tests passed, 0 failedcargo check -p browser— clean, no warningscargo test -p browser— 47 passed, 0 failedpnpm exec eslint --max-warnings 0on every file this branch touches — cleanThe full vitest run caught
src/components/Dropdown/index.test.ts > registers a visible controlled menu as a webview-blocking overlayfailing against the positioned-gate change. That is fixed in8cd6bd36f; the suite is green.Manual checks on macOS, still outstanding:
getPopupContainer) over the browser is masked, not painted overNot run: any Windows or Linux check. Those platforms take the unchanged offscreen fallback path.
No screenshots or recordings are attached yet. These are compositor-level visual changes, so the manual checks above are the meaningful evidence and are not substitutable by unit tests.