Skip to content

Keep the mobile shell stable across tab changes - #30

Merged
ragojose merged 1 commit into
mainfrom
ragojose/fix-dmg-black-flick
Aug 11, 2026
Merged

ragojose merged 1 commit into
mainfrom
ragojose/fix-dmg-black-flick

Conversation

@ragojose

Copy link
Copy Markdown
Owner

What

Two viewport-mode bugs, both reported from the running app.

1. + / ⌘T in mobile mode snapped the window back to desktop. OnTabCreated ended with UpdateWindowForViewportMode(TabIsMobile(browser_id), …), and a brand-new tab is always desktop — so adopting it animated the 512×896 mobile shell back to the saved desktop frame and re-laid the chrome on every new tab. New tabs now inherit the outgoing active tab's viewport mode (the outgoing tab is still the active one during adoption; OnAfterCreated promotes the new browser only after adoption returns).

The overrides are applied a UI-thread turn later, via BroHandler::AdoptTabMobileEmulation. Calling ExecuteDevToolsMethod on a browser still inside its own OnAfterCreated is a deterministic SIGSEGV in CEF 151 (EXC_BAD_ACCESS on CrBrowserMain, OnAfterCreated → SetTabMobileEmulation → CefBrowserHostCToCpp::ExecuteDevToolsMethod) — hit while developing this, so the mode is recorded synchronously (the shell lays out from it in the same turn) and only the DevTools calls are deferred.

2. Activating a desktop tab from the mobile shell scrambled the tab strip. The activation spring moves pill frames through the animator; the window resize that follows re-laid the strip with plain frame sets, which do not stop an in-flight animation. Those pills kept springing to the targets computed for the 512pt strip inside a now-1200pt window and overlapped each other. Unanimated layouts now assign through the animator inside a zero-duration NSAnimationContext group, which both lands the frame and cancels the spring.

Measured pill geometry, 5 tabs, after switching from the mobile shell to a desktop tab:

before after
pills 1–3 28pt @468, 28pt @503, 110pt @539 (overlapping) 158pt @468, @634, @800
pills 4–5 158pt @967, @1133 158pt @967, @1133

Pills 4–5 were already correct because their slots didn't move on activation, so no animation was in flight for them — that asymmetry is what identified the cause.

Also in this branch

Uncommitted work that was already in the workspace, included here rather than stranded:

  • Reading Mode crash gate moves to ImmersiveReadAnything; the previous ReadAnything name is not a base::Feature in CEF 151 (verified against the shipped framework), and Chromium silently ignores unknown disable-features names, so that switch was a no-op.
  • Tab strip gains horizontal scrolling with the search control pinned at the trailing edge.

Testing

Driven through the accessibility API against a renamed copy of the build on an isolated profile, with page state read over CDP:

  • + in mobile mode: window holds 512×896, toggles read Mobile, four consecutive new tabs, no crash reports.
  • the inherited tab is genuinely emulated — navigating it reports W=390 DPR=2 TOUCH=5 UA=iphone.
  • desktop unchanged: + from a desktop tab stays 1200×800.
  • mobile ↔ desktop tab switches round-trip cleanly, 512×896 ↔ 1200×800, pills evenly spaced and stable both immediately and after settling; confirmed visually from a window capture.

🤖 Generated with Claude Code

New tabs now inherit the active tab's viewport mode. The shell hugs the
mobile viewport for as long as a mobile-emulated tab is active, so a
desktop-mode new tab snapped the window back to the desktop frame on
every Cmd+T / + click. Adoption records the inherited mode synchronously
(the shell and the tab's container lay out from it in the same turn) and
posts the DevTools overrides to the next UI-thread turn: calling
ExecuteDevToolsMethod on a browser still inside its own OnAfterCreated
dereferences null in CEF 151 and takes the process down.

Activating a desktop tab from the mobile shell also left the tab strip
overlapping itself. The activation spring animates pill frames through
the animator; the window resize that follows re-laid the strip with plain
`frame` sets, which do not stop an animation already in flight, so those
pills sprang back to the targets computed for the 512pt strip inside a
1200pt window. Only pills whose slot happened not to move came out right.
Unanimated layouts now go through the animator inside a zero-duration
group, which lands the frame and cancels the spring.

Also in this branch: the Reading Mode crash gate moves to
ImmersiveReadAnything (the earlier "ReadAnything" name is not a
base::Feature in CEF 151, so that switch was a silent no-op), and the tab
strip gains horizontal scrolling with a pinned search control.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ragojose
ragojose merged commit e585088 into main Aug 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant