Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions src/bro_app.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,20 @@ void BroApp::OnBeforeCommandLineProcessing(
command_line->AppendSwitch("ignore-gpu-blocklist");

// Chrome's Reading Mode ships in CEF without the profile plumbing it
// expects: its ReadAnythingSoftNavigationObserver dereferences a null
// coordinator the moment any page performs a soft navigation (History API /
// same-document), taking the whole browser process down. Symbolicated from
// a field crash on wakawaka.world, 2026-08-11 (build 85). Disabling the
// feature removes the observer; merge with any existing list rather than
// clobbering the features CEF disables on its own.
// expects: its page-load-metrics observer dereferences a null side-panel
// coordinator on ordinary page loads and soft navigations, taking the whole
// browser process down. Field crashes on wakawaka.world (build 85) and
// basement.studio (build 88).
//
// The gate is ImmersiveReadAnything, which is enabled by default. An earlier
// attempt disabled "ReadAnything", which is not a base::Feature at all in CEF
// 151 -- verified by scanning every feature struct in the shipped framework;
// the only "ReadAnything" string there is a SidePanelEntry::Id name. Chromium
// ignores unknown names in disable-features, so that switch was a no-op. If
// this name ever needs changing, verify it against the CEF binary first.
//
// Merge with any existing list rather than clobbering the features CEF
// disables on its own.
const char kDisableFeatures[] = "disable-features";
std::string disabled =
command_line->HasSwitch(kDisableFeatures)
Expand All @@ -45,7 +53,7 @@ void BroApp::OnBeforeCommandLineProcessing(
if (!disabled.empty()) {
disabled += ",";
}
disabled += "ReadAnything";
disabled += "ImmersiveReadAnything";
command_line->AppendSwitchWithValue(kDisableFeatures, disabled);
}

Expand Down
31 changes: 31 additions & 0 deletions src/bro_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,37 @@ void BroHandler::SetTabMobileEmulation(int browser_id, bool enabled) {
}
}

void BroHandler::AdoptTabMobileEmulation(int browser_id) {
CEF_REQUIRE_UI_THREAD();

if (IsTabMobile(browser_id)) {
return;
}
// Recorded synchronously: the caller lays the shell and the tab's container
// out from this state in the same turn.
mobile_tab_ids_.insert(browser_id);

CefPostTask(TID_UI,
base::BindOnce(&BroHandler::ApplyPendingMobileEmulation, this,
browser_id));
}

void BroHandler::ApplyPendingMobileEmulation(int browser_id) {
CEF_REQUIRE_UI_THREAD();

// The tab may have closed or been switched back to desktop while the task
// was queued.
if (!IsTabMobile(browser_id)) {
return;
}
CefRefPtr<CefBrowser> browser = browser_registry_.GetById(browser_id);
if (browser) {
// No reload: a freshly adopted tab has not made its first user-driven
// navigation yet, which is when the user agent override takes effect.
ApplyEmulationToBrowser(browser, /*mobile=*/true, /*reload=*/false);
}
}

void BroHandler::ReloadTab(int browser_id) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI,
Expand Down
11 changes: 11 additions & 0 deletions src/bro_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ class BroHandler : public CefClient,
return mobile_tab_ids_.count(browser_id) > 0;
}

// Marks a just-created tab as mobile so the shell can lay it out
// immediately, and applies the DevTools overrides on a later UI-thread
// turn. Used by the adoption path, which runs inside OnAfterCreated --
// ExecuteDevToolsMethod on a browser still inside its creation callback
// dereferences null in CEF 151 (SIGSEGV on CrBrowserMain).
void AdoptTabMobileEmulation(int browser_id);

// Reload a tab. Used to apply a pending user agent override once the
// viewport toggle animation has finished (the UA only takes effect on the
// next navigation).
Expand Down Expand Up @@ -227,6 +234,10 @@ class BroHandler : public CefClient,
bool mobile,
bool reload);

// Deferred half of AdoptTabMobileEmulation: applies the overrides unless the
// tab was closed or switched back to desktop in the meantime.
void ApplyPendingMobileEmulation(int browser_id);

// True if using Alloy style (native windows)
const bool is_alloy_style_;

Expand Down
47 changes: 44 additions & 3 deletions src/bro_mac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1610,8 +1610,29 @@ - (void)createApplication:(id)object {
g_main_window.delegate = self;
g_main_window.title = @"Bro Computer";

// Show the window
// Order the window while transparent so the behind-window material can
// attach and AppKit can commit the complete shell before any pixels become
// visible. Cold launches from a DMG otherwise expose the window server's
// black initial backing store for a frame before the glass surface resolves.
g_main_window.alphaValue = 0.0;
[g_main_window makeKeyAndOrderFront:nil];
[g_main_window.contentView layoutSubtreeIfNeeded];
[g_main_window displayIfNeeded];
[CATransaction flush];

// Reveal without animation on the next main-queue turn, after the initial
// layer transaction has been submitted. Keep this independent of CEF's
// asynchronous browser creation so a slow or failed browser startup cannot
// leave the native window invisible.
dispatch_async(dispatch_get_main_queue(), ^{
if (!g_main_window) {
return;
}
[g_main_window.contentView layoutSubtreeIfNeeded];
[g_main_window displayIfNeeded];
[CATransaction flush];
g_main_window.alphaValue = 1.0;
});

// Create the CEF browser in our window
[self performSelectorOnMainThread:@selector(createBrowserInWindow)
Expand Down Expand Up @@ -2528,6 +2549,23 @@ bool OnTabCreated(int browser_id, const std::string& url, void* native_view) {
[g_blank_tab_ids addObject:@(browser_id)];
}

// A tab opened while a mobile-emulated tab is active inherits that mode.
// The shell hugs the mobile viewport for as long as such a tab is active,
// so a desktop-mode new tab would snap the window back to the desktop frame
// and re-lay the chrome on every ⌘T / + click. The outgoing tab is still
// the active one here: OnAfterCreated promotes this browser only after
// adoption returns. Applied before the tab bar work below so the toolbar's
// viewport toggles (updated from setActiveTab:) show the inherited mode.
// The DevTools overrides themselves land a turn later -- see
// AdoptTabMobileEmulation; issuing them from inside OnAfterCreated crashes.
BroHandler* creating_handler = BroHandler::GetInstance();
int outgoing_id =
creating_handler ? creating_handler->GetActiveBrowserId() : -1;
if (creating_handler && outgoing_id >= 0 && outgoing_id != browser_id &&
creating_handler->IsTabMobile(outgoing_id)) {
creating_handler->AdoptTabMobileEmulation(browser_id);
}

// Add tab to tab bar
if (g_tab_bar) {
[g_tab_bar addTabWithBrowserId:browser_id title:kBroBlankTabTitle];
Expand All @@ -2538,8 +2576,11 @@ bool OnTabCreated(int browser_id, const std::string& url, void* native_view) {
UpdateTabContainerVisibility(browser_id);

// A newly adopted tab becomes active without going through
// OnActiveTabChanged; the shell follows its viewport mode (desktop).
UpdateWindowForViewportMode(TabIsMobile(browser_id), YES);
// OnActiveTabChanged; the shell follows its viewport mode (inherited from
// the tab it replaced, above). While split the shell stays desktop-shaped,
// as it does on every other tab switch.
UpdateWindowForViewportMode(SplitActive() ? NO : TabIsMobile(browser_id),
YES);

if (shouldFocusAddress) {
// Let adoption and layout finish before asking AppKit for its field
Expand Down
10 changes: 7 additions & 3 deletions src/bro_mac_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ extern NSTextField* BroHoverCardLabel(NSFont* font, CGFloat whiteAlpha);
// the active pill reveals it only while editing.
@interface BroTabView : NSView
@property (nonatomic, assign) int browserId;
// The tab bar remains the interaction owner even though pills live inside its
// scroll view's document view rather than as direct children.
@property (nonatomic, weak) BroTabBar* owningTabBar;
@property (nonatomic, strong) NSImageView* faviconView;
@property (nonatomic, strong) BroShimmerTextView* titleLabel;
// Each pill owns its editor for its entire lifetime. Only the active pill may
Expand Down Expand Up @@ -336,9 +339,10 @@ extern NSTextField* BroHoverCardLabel(NSFont* font, CGFloat whiteAlpha);

#pragma mark - BroTabBar

// The tab strip lives INSIDE the toolbar row: each tab is a pill (favicon +
// host + close), the active pill hosts the editable address field, and the
// "+" button trails the last pill.
// The tab strip lives INSIDE the toolbar row: tab pills scroll horizontally,
// the active pill hosts the editable address field, the "+" trails the last
// pill until the pills overflow, and the search control stays pinned at the
// trailing edge.
@interface BroTabBar : NSView
@property (nonatomic, strong) NSMutableArray<BroTabView*>* tabs;
@property (nonatomic, strong) BroHoverButton* addTabButton;
Expand Down
Loading
Loading