glass/browser/session/
targets.rs1use super::*;
9
10const MAX_CONCURRENT_TARGETS: usize = 4;
12
13impl BrowserSession {
14 pub async fn with_targets<F, Fut>(&self, n: usize, f: F) -> BrowserResult<Fut::Output>
24 where
25 F: FnOnce(Vec<PageTargetInfo>) -> Fut,
26 Fut: std::future::Future,
27 {
28 if n == 0 || n > MAX_CONCURRENT_TARGETS {
29 return Err(
30 format!("with_targets: n must be 1..={MAX_CONCURRENT_TARGETS}, got {n}").into(),
31 );
32 }
33
34 let previous_active = {
36 let topology = self.topology.lock().await;
37 topology.active_target_id.clone()
38 };
39
40 let mut targets = Vec::with_capacity(n);
42 for i in 0..n {
43 let target = match self.create_target("about:blank").await {
44 Ok(target) => target,
45 Err(e) => {
46 let _ = cleanup_targets(self, &targets).await;
47 return Err(
48 format!("with_targets: failed to create target {i}/{n}: {e}").into(),
49 );
50 }
51 };
52 targets.push(target);
53 }
54
55 let result = f(targets.clone()).await;
57
58 if let Err(e) = cleanup_targets(self, &targets).await {
60 tracing::warn!("with_targets: cleanup failed: {e}");
61 }
62
63 if let Some(ref prev_id) = previous_active {
65 let exists = self
66 .list_targets()
67 .await
68 .map(|targets| targets.iter().any(|t| &t.id == prev_id))
69 .unwrap_or(false);
70 if exists {
71 let _ = self.select_target(prev_id).await;
72 }
73 }
74
75 Ok(result)
76 }
77}
78
79async fn cleanup_targets(
80 session: &BrowserSession,
81 targets: &[PageTargetInfo],
82) -> BrowserResult<()> {
83 for target in targets {
84 let _ = session.close_target(&target.id).await;
86 }
87 Ok(())
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn max_concurrent_targets_is_4() {
96 assert_eq!(MAX_CONCURRENT_TARGETS, 4);
97 }
98
99 #[test]
100 fn max_concurrent_targets_is_reasonable() {
101 const { assert!(MAX_CONCURRENT_TARGETS >= 1) };
103 const { assert!(MAX_CONCURRENT_TARGETS <= 16) };
105 }
106
107 #[test]
108 fn max_concurrent_targets_is_power_of_two() {
109 assert_eq!(MAX_CONCURRENT_TARGETS.count_ones(), 1);
111 }
112}