@@ -1372,10 +1372,47 @@ async fn record_client_stream_stats(
13721372 ) ) ;
13731373 }
13741374
1375+ apply_stream_client_foreground_from_stats ( & state, & payload) ;
13751376 state. metrics . record_client_stream_stats ( payload) ;
13761377 Ok ( json ( json_value ! ( { "ok" : true } ) ) )
13771378}
13781379
1380+ pub ( crate ) fn apply_stream_client_foreground_from_stats (
1381+ state : & AppState ,
1382+ stats : & ClientStreamStats ,
1383+ ) {
1384+ let Some ( foreground) = client_stats_foreground ( stats) else {
1385+ return ;
1386+ } ;
1387+ let Some ( udid) = stats
1388+ . udid
1389+ . as_deref ( )
1390+ . map ( str:: trim)
1391+ . filter ( |value| !value. is_empty ( ) )
1392+ else {
1393+ return ;
1394+ } ;
1395+ let client_id = stats. client_id . trim ( ) ;
1396+ if client_id. is_empty ( ) {
1397+ return ;
1398+ }
1399+ let ( any_foreground, changed) = state. stream_clients . record ( udid, client_id, foreground) ;
1400+ if changed {
1401+ if let Some ( session) = state. registry . get ( udid) {
1402+ session. set_client_foreground ( any_foreground) ;
1403+ }
1404+ }
1405+ }
1406+
1407+ fn client_stats_foreground ( stats : & ClientStreamStats ) -> Option < bool > {
1408+ if stats. kind != "page" {
1409+ return None ;
1410+ }
1411+ let visible = stats. visibility_state . as_deref ( ) == Some ( "visible" ) ;
1412+ let focused = stats. focused ?;
1413+ Some ( visible && focused)
1414+ }
1415+
13791416async fn native_inspector_connect (
13801417 State ( state) : State < AppState > ,
13811418 websocket : WebSocketUpgrade ,
@@ -2558,6 +2595,7 @@ fn handle_h264_socket_message(
25582595 match message {
25592596 H264SocketMessage :: ClientStats { stats } => {
25602597 if !stats. client_id . trim ( ) . is_empty ( ) && !stats. kind . trim ( ) . is_empty ( ) {
2598+ apply_stream_client_foreground_from_stats ( state, & stats) ;
25612599 state. metrics . record_client_stream_stats ( * stats) ;
25622600 }
25632601 }
@@ -6048,9 +6086,9 @@ async fn accessibility_snapshot(
60486086mod tests {
60496087 use super :: {
60506088 accessibility_point_snapshot, attach_tree_metadata, available_sources_for_snapshot,
6051- best_inspector_session, compact_accessibility_snapshot , element_matches_selector ,
6052- first_matching_element , inspector_available_sources , inspector_metadata ,
6053- inspector_session_from_published, inspector_session_score,
6089+ best_inspector_session, client_stats_foreground , compact_accessibility_snapshot ,
6090+ element_matches_selector , first_matching_element , inspector_available_sources ,
6091+ inspector_metadata , inspector_session_from_published, inspector_session_score,
60546092 is_inspector_agent_transport_path, normalize_inspector_node,
60556093 normalize_screen_point_from_snapshot, normalized_gesture_coordinates,
60566094 parse_lsof_tcp_listener, parse_ui_application_service_line, resolved_stream_quality_limits,
@@ -6062,6 +6100,7 @@ mod tests {
60626100 SOURCE_NATIVE_SCRIPT , SOURCE_REACT_NATIVE , SOURCE_SWIFTUI , SOURCE_UIKIT ,
60636101 } ;
60646102 use crate :: inspector:: PublishedInspector ;
6103+ use crate :: metrics:: counters:: ClientStreamStats ;
60656104 use crate :: transport:: packet:: FramePacket ;
60666105 use bytes:: Bytes ;
60676106 use serde_json:: { json, Value } ;
@@ -6101,6 +6140,45 @@ mod tests {
61016140 assert_eq ! ( registry. remove( "udid" , "hidden" ) , ( false , false ) ) ;
61026141 }
61036142
6143+ #[ test]
6144+ fn client_stats_foreground_requires_visible_and_focused_page ( ) {
6145+ let page_stats =
6146+ |visibility_state : Option < & str > , focused : Option < bool > | ClientStreamStats {
6147+ client_id : "client" . to_owned ( ) ,
6148+ kind : "page" . to_owned ( ) ,
6149+ visibility_state : visibility_state. map ( ToOwned :: to_owned) ,
6150+ focused,
6151+ ..Default :: default ( )
6152+ } ;
6153+
6154+ assert_eq ! (
6155+ client_stats_foreground( & page_stats( Some ( "visible" ) , Some ( true ) ) ) ,
6156+ Some ( true )
6157+ ) ;
6158+ assert_eq ! (
6159+ client_stats_foreground( & page_stats( Some ( "visible" ) , Some ( false ) ) ) ,
6160+ Some ( false )
6161+ ) ;
6162+ assert_eq ! (
6163+ client_stats_foreground( & page_stats( Some ( "hidden" ) , Some ( true ) ) ) ,
6164+ Some ( false )
6165+ ) ;
6166+ assert_eq ! (
6167+ client_stats_foreground( & page_stats( Some ( "visible" ) , None ) ) ,
6168+ None
6169+ ) ;
6170+ assert_eq ! (
6171+ client_stats_foreground( & ClientStreamStats {
6172+ client_id: "client" . to_owned( ) ,
6173+ kind: "webrtc" . to_owned( ) ,
6174+ focused: Some ( true ) ,
6175+ visibility_state: Some ( "visible" . to_owned( ) ) ,
6176+ ..Default :: default ( )
6177+ } ) ,
6178+ None
6179+ ) ;
6180+ }
6181+
61046182 #[ test]
61056183 fn selector_matching_uses_identifier_label_and_type_aliases ( ) {
61066184 let snapshot = accessibility_snapshot ( ) ;
0 commit comments