From 0d755af005d3fa9e16f05c0948044132f957d179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sat, 16 May 2026 05:03:43 -0400 Subject: [PATCH] perf(app): suppress repeated web metadata updates --- crates/ely_app/src/shell/web_surface.rs | 6 +- .../ely_app/src/shell/web_surface_metadata.rs | 73 ++++++++++++++++++- .../src/shell/web_surface_runtime_tests.rs | 2 +- crates/ely_app/src/shell/web_surface_state.rs | 12 ++- 4 files changed, 83 insertions(+), 10 deletions(-) diff --git a/crates/ely_app/src/shell/web_surface.rs b/crates/ely_app/src/shell/web_surface.rs index 3041a7e..0d72346 100644 --- a/crates/ely_app/src/shell/web_surface.rs +++ b/crates/ely_app/src/shell/web_surface.rs @@ -10,7 +10,6 @@ use super::{ web_surface_cadence::IDLE_POLL_INTERVAL, web_surface_frame::WebSurfaceFrame, web_surface_geometry::{WebSurfaceClickPoint, WebSurfaceScrollDelta, WebSurfaceSize}, - web_surface_metadata::WebSurfacePageMetadata, web_surface_permissions::WebSurfaceSitePermission, web_surface_runtime::{WebSurfaceRuntime, WebSurfaceRuntimeFrame}, web_surface_state::{ @@ -118,9 +117,8 @@ impl WebSurfaceStore { } continue; } - result - .page_metadata - .extend(WebSurfacePageMetadata::from_frame(&tab_id, &frame)); + let metadata = self.surface_mut(&tab_id).changed_page_metadata(&tab_id, &frame); + result.page_metadata.extend(metadata); if self .surfaces .get(&tab_id) diff --git a/crates/ely_app/src/shell/web_surface_metadata.rs b/crates/ely_app/src/shell/web_surface_metadata.rs index 2b3368f..a2fa801 100644 --- a/crates/ely_app/src/shell/web_surface_metadata.rs +++ b/crates/ely_app/src/shell/web_surface_metadata.rs @@ -2,6 +2,30 @@ use ely_domain::TabId; use super::web_surface_frame::WebSurfaceFrame; +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub(super) struct WebSurfaceMetadataTracker { + last_title: Option, + last_loaded_url: Option, +} + +impl WebSurfaceMetadataTracker { + pub(super) fn changed_metadata_for( + &mut self, + tab_id: &TabId, + frame: &WebSurfaceFrame, + ) -> Option { + let title = frame.title().map(str::to_string); + let loaded_url = frame.loaded_url().map(str::to_string); + if self.last_title == title && self.last_loaded_url == loaded_url { + return None; + } + + self.last_title = title.clone(); + self.last_loaded_url = loaded_url.clone(); + WebSurfacePageMetadata::from_parts(tab_id, title, loaded_url) + } +} + /// One page's worth of metadata observed in a Ready frame. The /// controller applies these to the `BrowserTab` after the frame has /// been swapped into the surface state. Title and favicon are @@ -15,10 +39,13 @@ pub(super) struct WebSurfacePageMetadata { } impl WebSurfacePageMetadata { - pub(super) fn from_frame(tab_id: &TabId, frame: &WebSurfaceFrame) -> Option { - let title = frame.title().map(str::to_string); - let favicon_url = frame - .loaded_url() + fn from_parts( + tab_id: &TabId, + title: Option, + loaded_url: Option, + ) -> Option { + let favicon_url = loaded_url + .as_deref() .and_then(|loaded| ely_domain::UrlText::parse(loaded).ok()) .and_then(|url| url.favicon_url()); if title.is_none() && favicon_url.is_none() { @@ -27,3 +54,41 @@ impl WebSurfacePageMetadata { Some(Self { tab_id: tab_id.clone(), title, favicon_url }) } } + +#[cfg(test)] +mod tests { + use std::error::Error; + + use ely_domain::TabId; + + use super::*; + use crate::{ + services::servo_live::ServoLiveFrame, + shell::{web_surface_frame::WebSurfaceFrame, web_surface_geometry::WebSurfaceScrollOffset}, + }; + + #[test] + fn repeated_frame_metadata_emits_once() -> Result<(), Box> { + let tab_id = TabId::new(); + let frame = frame()?; + let mut tracker = WebSurfaceMetadataTracker::default(); + + let first = tracker.changed_metadata_for(&tab_id, &frame); + let second = tracker.changed_metadata_for(&tab_id, &frame); + + assert!(first.is_some()); + assert_eq!(second, None); + Ok(()) + } + + fn frame() -> Result> { + let rgba_pixel = vec![255u8, 255, 255, 255]; + let live = ServoLiveFrame::for_test(1, 1, rgba_pixel); + Ok(WebSurfaceFrame::from_live_frame( + "https://example.com/".to_string(), + WebSurfaceScrollOffset::default(), + 100, + live, + )?) + } +} diff --git a/crates/ely_app/src/shell/web_surface_runtime_tests.rs b/crates/ely_app/src/shell/web_surface_runtime_tests.rs index f5c60d4..eaad13b 100644 --- a/crates/ely_app/src/shell/web_surface_runtime_tests.rs +++ b/crates/ely_app/src/shell/web_surface_runtime_tests.rs @@ -176,7 +176,7 @@ fn identical_ready_software_frame_keeps_tick_unchanged() -> Result<(), String> { let second_tick = store.tick(&visible_tabs); assert!(!second_tick.changed); - assert_eq!(second_tick.page_metadata.len(), 1); + assert_eq!(second_tick.page_metadata.len(), 0); assert_eq!(second_tick.url_changes.len(), 1); assert_eq!(REPEATED_FRAME_ENSURE_COUNT.load(Ordering::SeqCst), 2); Ok(()) diff --git a/crates/ely_app/src/shell/web_surface_state.rs b/crates/ely_app/src/shell/web_surface_state.rs index 3bf460c..4f68ccc 100644 --- a/crates/ely_app/src/shell/web_surface_state.rs +++ b/crates/ely_app/src/shell/web_surface_state.rs @@ -9,7 +9,7 @@ use super::{ web_surface_geometry::{ WebSurfaceClickPoint, WebSurfaceScrollDelta, WebSurfaceScrollOffset, WebSurfaceSize, }, - web_surface_metadata::WebSurfacePageMetadata, + web_surface_metadata::{WebSurfaceMetadataTracker, WebSurfacePageMetadata}, web_surface_permissions::WebSurfaceSitePermission, web_surface_runtime::WebSurfaceUrlChange, }; @@ -137,6 +137,7 @@ pub(super) struct PerTabSurface { pub(super) typed_text: Option, pub(super) state: Option, last_input_flushed_at: Option, + metadata_tracker: WebSurfaceMetadataTracker, } impl PerTabSurface { @@ -155,6 +156,7 @@ impl PerTabSurface { typed_text: None, state: None, last_input_flushed_at: None, + metadata_tracker: WebSurfaceMetadataTracker::default(), } } @@ -195,6 +197,14 @@ impl PerTabSurface { } } + pub(super) fn changed_page_metadata( + &mut self, + tab_id: &TabId, + frame: &WebSurfaceFrame, + ) -> Option { + self.metadata_tracker.changed_metadata_for(tab_id, frame) + } + fn has_pending_input(&self) -> bool { self.hover_point.is_some() || self.click_point.is_some()