firefox_webdriver/browser/tab/
elements.rs1use std::sync::Arc;
4use std::time::Duration;
5
6use parking_lot::Mutex as ParkingMutex;
7use tokio::sync::oneshot;
8use tokio::time::timeout;
9use tracing::debug;
10
11use crate::browser::Element;
12use crate::browser::selector::By;
13use crate::error::{Error, Result};
14use crate::identifiers::{ElementId, SubscriptionId};
15use crate::protocol::event::ParsedEvent;
16use crate::protocol::{Command, ElementCommand, Event};
17
18use super::Tab;
19
20const DEFAULT_WAIT_TIMEOUT: Duration = Duration::from_secs(30);
26
27impl Tab {
32 pub async fn find_element(&self, by: By) -> Result<Element> {
52 let command = Command::Element(ElementCommand::Find {
53 strategy: by.strategy().to_string(),
54 value: by.value().to_string(),
55 parent_id: None,
56 });
57
58 let response = self.send_command(command).await?;
59
60 let element_id = response
61 .result
62 .as_ref()
63 .and_then(|v| v.get("elementId"))
64 .and_then(|v| v.as_str())
65 .ok_or_else(|| {
66 Error::element_not_found(
67 format!("{}:{}", by.strategy(), by.value()),
68 self.inner.tab_id,
69 self.inner.frame_id,
70 )
71 })?;
72
73 Ok(Element::new(
74 ElementId::new(element_id),
75 self.inner.tab_id,
76 self.inner.frame_id,
77 self.inner.session_id,
78 self.inner.window.clone(),
79 ))
80 }
81
82 pub async fn find_elements(&self, by: By) -> Result<Vec<Element>> {
93 let command = Command::Element(ElementCommand::FindAll {
94 strategy: by.strategy().to_string(),
95 value: by.value().to_string(),
96 parent_id: None,
97 });
98
99 let response = self.send_command(command).await?;
100
101 let elements = response
102 .result
103 .as_ref()
104 .and_then(|v| v.get("elementIds"))
105 .and_then(|v| v.as_array())
106 .map(|arr| {
107 arr.iter()
108 .filter_map(|v| v.as_str())
109 .map(|id| {
110 Element::new(
111 ElementId::new(id),
112 self.inner.tab_id,
113 self.inner.frame_id,
114 self.inner.session_id,
115 self.inner.window.clone(),
116 )
117 })
118 .collect()
119 })
120 .unwrap_or_default();
121
122 Ok(elements)
123 }
124}
125
126impl Tab {
131 pub async fn wait_for_element(&self, by: By) -> Result<Element> {
145 self.wait_for_element_timeout(by, DEFAULT_WAIT_TIMEOUT)
146 .await
147 }
148
149 pub async fn wait_for_element_timeout(
151 &self,
152 by: By,
153 timeout_duration: Duration,
154 ) -> Result<Element> {
155 debug!(
156 tab_id = %self.inner.tab_id,
157 strategy = by.strategy(),
158 value = by.value(),
159 timeout_ms = timeout_duration.as_millis(),
160 "Waiting for element"
161 );
162
163 let window = self.get_window()?;
164
165 let (tx, rx) = oneshot::channel::<Result<Element>>();
166 let tx = Arc::new(ParkingMutex::new(Some(tx)));
167 let expected_strategy = by.strategy().to_string();
168 let expected_value = by.value().to_string();
169 let tab_id = self.inner.tab_id;
170 let frame_id = self.inner.frame_id;
171 let session_id = self.inner.session_id;
172 let window_clone = self.inner.window.clone();
173 let tx_clone = Arc::clone(&tx);
174
175 window.inner.pool.set_event_handler(
176 window.inner.session_id,
177 Box::new(move |event: Event| {
178 if event.method.as_str() != "element.added" {
179 return None;
180 }
181
182 let parsed = event.parse();
183 if let ParsedEvent::ElementAdded {
184 strategy,
185 value,
186 element_id,
187 ..
188 } = parsed
189 && strategy == expected_strategy
190 && value == expected_value
191 {
192 let element = Element::new(
193 ElementId::new(&element_id),
194 tab_id,
195 frame_id,
196 session_id,
197 window_clone.clone(),
198 );
199
200 if let Some(tx) = tx_clone.lock().take() {
201 let _ = tx.send(Ok(element));
202 }
203 }
204
205 None
206 }),
207 );
208
209 let command = Command::Element(ElementCommand::Subscribe {
210 strategy: by.strategy().to_string(),
211 value: by.value().to_string(),
212 one_shot: true,
213 timeout: Some(timeout_duration.as_millis() as u64),
214 });
215 let response = self.send_command(command).await?;
216
217 if let Some(element_id) = response
219 .result
220 .as_ref()
221 .and_then(|v| v.get("elementId"))
222 .and_then(|v| v.as_str())
223 {
224 window
225 .inner
226 .pool
227 .clear_event_handler(window.inner.session_id);
228
229 return Ok(Element::new(
230 ElementId::new(element_id),
231 self.inner.tab_id,
232 self.inner.frame_id,
233 self.inner.session_id,
234 self.inner.window.clone(),
235 ));
236 }
237
238 let result = timeout(timeout_duration, rx).await;
239
240 window
241 .inner
242 .pool
243 .clear_event_handler(window.inner.session_id);
244
245 match result {
246 Ok(Ok(element)) => element,
247 Ok(Err(_)) => Err(Error::protocol("Channel closed unexpectedly")),
248 Err(_) => Err(Error::Timeout {
249 operation: format!("wait_for({}:{})", by.strategy(), by.value()),
250 timeout_ms: timeout_duration.as_millis() as u64,
251 }),
252 }
253 }
254
255 pub async fn on_element_added<F>(&self, by: By, callback: F) -> Result<SubscriptionId>
261 where
262 F: Fn(Element) + Send + Sync + 'static,
263 {
264 debug!(
265 tab_id = %self.inner.tab_id,
266 strategy = by.strategy(),
267 value = by.value(),
268 "Subscribing to element.added"
269 );
270
271 let window = self.get_window()?;
272
273 let expected_strategy = by.strategy().to_string();
274 let expected_value = by.value().to_string();
275 let tab_id = self.inner.tab_id;
276 let frame_id = self.inner.frame_id;
277 let session_id = self.inner.session_id;
278 let window_clone = self.inner.window.clone();
279 let callback = Arc::new(callback);
280
281 window.inner.pool.set_event_handler(
282 window.inner.session_id,
283 Box::new(move |event: Event| {
284 if event.method.as_str() != "element.added" {
285 return None;
286 }
287
288 let parsed = event.parse();
289 if let ParsedEvent::ElementAdded {
290 strategy,
291 value,
292 element_id,
293 ..
294 } = parsed
295 && strategy == expected_strategy
296 && value == expected_value
297 {
298 let element = Element::new(
299 ElementId::new(&element_id),
300 tab_id,
301 frame_id,
302 session_id,
303 window_clone.clone(),
304 );
305 callback(element);
306 }
307
308 None
309 }),
310 );
311
312 let command = Command::Element(ElementCommand::Subscribe {
313 strategy: by.strategy().to_string(),
314 value: by.value().to_string(),
315 one_shot: false,
316 timeout: None,
317 });
318
319 let response = self.send_command(command).await?;
320
321 let subscription_id = response
322 .result
323 .as_ref()
324 .and_then(|v| v.get("subscriptionId"))
325 .and_then(|v| v.as_str())
326 .ok_or_else(|| Error::protocol("No subscriptionId in response"))?;
327
328 Ok(SubscriptionId::new(subscription_id))
329 }
330
331 pub async fn on_element_removed<F>(&self, element_id: &ElementId, callback: F) -> Result<()>
333 where
334 F: Fn() + Send + Sync + 'static,
335 {
336 debug!(tab_id = %self.inner.tab_id, %element_id, "Watching for element removal");
337
338 let window = self.get_window()?;
339
340 let element_id_clone = element_id.as_str().to_string();
341 let callback = Arc::new(callback);
342
343 window.inner.pool.set_event_handler(
344 window.inner.session_id,
345 Box::new(move |event: Event| {
346 if event.method.as_str() != "element.removed" {
347 return None;
348 }
349
350 let parsed = event.parse();
351 if let ParsedEvent::ElementRemoved {
352 element_id: removed_id,
353 ..
354 } = parsed
355 && removed_id == element_id_clone
356 {
357 callback();
358 }
359
360 None
361 }),
362 );
363
364 let command = Command::Element(ElementCommand::WatchRemoval {
365 element_id: element_id.clone(),
366 });
367
368 self.send_command(command).await?;
369 Ok(())
370 }
371
372 pub async fn unsubscribe(&self, subscription_id: &SubscriptionId) -> Result<()> {
374 let command = Command::Element(ElementCommand::Unsubscribe {
375 subscription_id: subscription_id.as_str().to_string(),
376 });
377
378 self.send_command(command).await?;
379
380 if let Some(window) = &self.inner.window {
381 window
382 .inner
383 .pool
384 .clear_event_handler(window.inner.session_id);
385 }
386
387 Ok(())
388 }
389}