diff --git a/rivet/runtime/windows/src/chromium/view.rs b/rivet/runtime/windows/src/chromium/view.rs index b8001c0..3e3faa7 100644 --- a/rivet/runtime/windows/src/chromium/view.rs +++ b/rivet/runtime/windows/src/chromium/view.rs @@ -39,20 +39,6 @@ impl View { } } -/// The parent of a live view of the loaded build, when its parent pointer holds a view of the -/// browser module: an object whose first word points into the module. `None` for a root, an -/// unreadable place or a build that records no bounds offset. -pub(crate) unsafe fn parent_of(view: usize) -> Option { - let offset = View::parent_offset(crate::builds::get().layout.view_bounds_offset)?; - if !crate::chromium::memory::readable(view.checked_add(offset)?, 8) { - return None; - } - let parent = View::at(view).parent_address(offset); - let image = crate::chromium::image::recorded()?; - let table = crate::chromium::memory::read_word(parent)?; - (table > image.base && table < image.base + image.size).then_some(parent) -} - #[cfg(test)] mod tests { use super::*; diff --git a/rivet/runtime/windows/src/features/frame_layout.rs b/rivet/runtime/windows/src/features/frame_layout.rs index 875faeb..6cd592e 100644 --- a/rivet/runtime/windows/src/features/frame_layout.rs +++ b/rivet/runtime/windows/src/features/frame_layout.rs @@ -1018,11 +1018,6 @@ pub unsafe fn relayout(window: HWND) -> bool { let layout_immediately: unsafe extern "system" fn(*mut c_void) = std::mem::transmute(base + bv().layout_immediately); layout_immediately(view as *mut c_void); - // The non-client view above the frame view gives the client view the bounds the frame - // view leaves it, so a new appbar width moves the page only when it lays out too. - if let Some(parent) = crate::chromium::view::parent_of(view) { - layout_immediately(parent as *mut c_void); - } true } None => false, diff --git a/rivet/runtime/windows/src/frame.rs b/rivet/runtime/windows/src/frame.rs index 722d5d7..def3888 100644 --- a/rivet/runtime/windows/src/frame.rs +++ b/rivet/runtime/windows/src/frame.rs @@ -2028,11 +2028,6 @@ unsafe fn sync_strip(window: HWND) { unsafe fn relayout(window: HWND) { // The frame view lays out again on request, which also works for a maximized window. crate::features::vertical_tabs::invalidate_strip(window); - // The frame view lays its client view out again only when the client view's bounds change, - // and a new appbar width can leave them as they were while the page area must move. The - // browser view lays its page area out again here, whether or not the frame view's own layout - // is recorded, as on Brave and Whale the page otherwise waits for the next input. - crate::hooks::page_area::relayout(window); if frame_layout::relayout(window) { if is_waiting(window) { crate::trace::trace("attach: asked the frame view to lay out again"); @@ -2146,16 +2141,82 @@ unsafe fn nudge(window: HWND, why: &str) -> bool { /// Hides the docked appbar so the page takes its width, or docks it again. unsafe fn toggle_appbar(window: HWND) { close_menu(window); + let before = frame_layout::panel_bounds(window).map(|panel| panel.x); edit_layout(window, |layout| layout.toggle()); crate::trace::trace(&format!("appbar: hidden {}", layout_of(window).collapsed)); sync_strip(window); SLIDE.with(|slides| slides.borrow_mut().remove(&(window as isize))); KillTimer(window, PEEK_TIMER); relayout(window); + let after = frame_layout::panel_bounds(window).map(|panel| panel.x); + if needs_size_nudge(before, after, sizing_state(window)) { + nudge_size(window, before, after); + } // The panel moves with the appbar, and the top bar with it. request_bar_paint(window); } +/// Whether a fold or show must be laid out by a size change: the browser laid the window out +/// again, yet its page panel did not move, as Brave and Whale keep the client area they laid out +/// until the window's size changes. Only a window of its own size is resized: never a minimized, +/// maximized, snapped or fullscreen one. +fn needs_size_nudge(before: Option, after: Option, state: NudgeState) -> bool { + before.is_some() + && before == after + && !state.minimized + && !state.maximized + && !state.snapped + && !state.fullscreen +} + +/// The window's size state, for a size change after a fold or show. +unsafe fn sizing_state(window: HWND) -> NudgeState { + use windows_sys::Win32::UI::WindowsAndMessaging::IsWindowArranged; + NudgeState { + minimized: IsIconic(window) != 0, + maximized: IsZoomed(window) != 0, + snapped: IsWindowArranged(window) != 0, + fullscreen: GetWindowLongPtrW(window, GWL_STYLE) as u32 & WS_THICKFRAME == 0, + ..NudgeState::default() + } +} + +/// Shrinks the window by one pixel without drawing and puts its exact rect back, which lays the +/// browser's root view out at once, and traces what happened. +unsafe fn nudge_size(window: HWND, before: Option, after: Option) { + use windows_sys::Win32::UI::WindowsAndMessaging::{GetWindowRect, SWP_NOREDRAW}; + let mut rect: RECT = std::mem::zeroed(); + GetWindowRect(window, &mut rect); + let (width, height) = (rect.right - rect.left, rect.bottom - rect.top); + SetWindowPos( + window, + std::ptr::null_mut(), + 0, + 0, + width, + height - 1, + SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW, + ); + SetWindowPos( + window, + std::ptr::null_mut(), + rect.left, + rect.top, + width, + height, + SWP_NOZORDER | SWP_NOACTIVATE, + ); + let mut now: RECT = std::mem::zeroed(); + GetWindowRect(window, &mut now); + let restored = (now.left, now.top, now.right, now.bottom) + == (rect.left, rect.top, rect.right, rect.bottom); + crate::trace::trace(&format!( + "appbar: the panel stayed at {before:?} after {after:?}, so window {:#x} was resized by one pixel and back, rect restored {restored}, panel now at {:?}", + window as usize, + frame_layout::panel_bounds(window).map(|panel| panel.x) + )); +} + /// Shows or puts away the floating appbar of a hidden one, sliding it. unsafe fn set_peek(window: HWND, on: bool) { let changed = edit_layout(window, |layout| { @@ -4710,6 +4771,43 @@ unsafe fn handle(window: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> } } +#[cfg(test)] +mod nudge_tests { + use super::*; + + #[test] + fn fr2_a_fold_resizes_the_window_only_when_its_panel_did_not_move() { + let normal = NudgeState::default(); + // The relayout moved the panel: nothing more. + assert!(!needs_size_nudge(Some(268), Some(8), normal)); + // The panel stayed where it was: a size change lays it out. + assert!(needs_size_nudge(Some(268), Some(268), normal)); + // No panel known yet: the window's own first layout comes. + assert!(!needs_size_nudge(None, None, normal)); + // Never a window without a size of its own. + for state in [ + NudgeState { + minimized: true, + ..normal + }, + NudgeState { + maximized: true, + ..normal + }, + NudgeState { + snapped: true, + ..normal + }, + NudgeState { + fullscreen: true, + ..normal + }, + ] { + assert!(!needs_size_nudge(Some(268), Some(268), state)); + } + } +} + #[cfg(test)] mod laps_tests { use super::*; diff --git a/rivet/runtime/windows/src/hooks/page_area.rs b/rivet/runtime/windows/src/hooks/page_area.rs index f141114..aac6672 100644 --- a/rivet/runtime/windows/src/hooks/page_area.rs +++ b/rivet/runtime/windows/src/hooks/page_area.rs @@ -279,14 +279,6 @@ pub(crate) unsafe fn relayout(window: HWND) { }) { let layout: unsafe extern "system" fn(*mut c_void) = std::mem::transmute(base + row().layout_immediately); - // The browser view sits in the client view, below the non-client view that gives the - // client view its bounds. That view is laid out first, so a new appbar width reaches the - // page even where the frame view's own layout is not recorded, as on Whale. - if let Some(non_client) = crate::chromium::view::parent_of(host) - .and_then(|client| crate::chromium::view::parent_of(client)) - { - layout(non_client as *mut c_void); - } layout(host as *mut c_void); } }