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 strategy_str = by.strategy().to_string();
168 let value_str = 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 let handler_key = format!("wait_for_element_{}_{}", strategy_str, value_str);
176 let handler_key_clone = handler_key.clone();
177 let expected_strategy = strategy_str.clone();
178 let expected_value = value_str.clone();
179
180 window.inner.pool.add_event_handler(
181 window.inner.session_id,
182 handler_key.clone(),
183 Box::new(move |event: Event| {
184 if event.method.as_str() != "element.added" {
185 return None;
186 }
187
188 let parsed = event.parse();
189 if let ParsedEvent::ElementAdded {
190 strategy,
191 value,
192 element_id,
193 ..
194 } = parsed
195 && strategy == expected_strategy
196 && value == expected_value
197 {
198 let element = Element::new(
199 ElementId::new(&*element_id),
200 tab_id,
201 frame_id,
202 session_id,
203 window_clone.clone(),
204 );
205
206 if let Some(tx) = tx_clone.lock().take() {
207 let _ = tx.send(Ok(element));
208 }
209 }
210
211 None
212 }),
213 );
214
215 let command = Command::Element(ElementCommand::Subscribe {
216 strategy: strategy_str,
217 value: value_str,
218 one_shot: true,
219 timeout: Some(timeout_duration.as_millis() as u64),
220 });
221 let response = self.send_command(command).await?;
222
223 if let Some(element_id) = response
225 .result
226 .as_ref()
227 .and_then(|v| v.get("elementId"))
228 .and_then(|v| v.as_str())
229 {
230 window
231 .inner
232 .pool
233 .remove_event_handler(window.inner.session_id, &handler_key_clone);
234
235 return Ok(Element::new(
236 ElementId::new(element_id),
237 self.inner.tab_id,
238 self.inner.frame_id,
239 self.inner.session_id,
240 self.inner.window.clone(),
241 ));
242 }
243
244 let result = timeout(timeout_duration, rx).await;
245
246 window
247 .inner
248 .pool
249 .remove_event_handler(window.inner.session_id, &handler_key_clone);
250
251 match result {
252 Ok(Ok(element)) => element,
253 Ok(Err(_)) => Err(Error::protocol("Channel closed unexpectedly")),
254 Err(_) => Err(Error::Timeout {
255 operation: format!("wait_for({}:{})", by.strategy(), by.value()),
256 timeout_ms: timeout_duration.as_millis() as u64,
257 }),
258 }
259 }
260
261 pub async fn on_element_added<F>(&self, by: By, callback: F) -> Result<SubscriptionId>
267 where
268 F: Fn(Element) + Send + Sync + 'static,
269 {
270 debug!(
271 tab_id = %self.inner.tab_id,
272 strategy = by.strategy(),
273 value = by.value(),
274 "Subscribing to element.added"
275 );
276
277 let window = self.get_window()?;
278
279 let strategy_str = by.strategy().to_string();
280 let value_str = by.value().to_string();
281 let tab_id = self.inner.tab_id;
282 let frame_id = self.inner.frame_id;
283 let session_id = self.inner.session_id;
284 let window_clone = self.inner.window.clone();
285 let callback = Arc::new(callback);
286
287 let handler_key = format!("on_element_added_{}_{}", strategy_str, value_str);
288 let expected_strategy = strategy_str.clone();
289 let expected_value = value_str.clone();
290
291 window.inner.pool.add_event_handler(
292 window.inner.session_id,
293 handler_key,
294 Box::new(move |event: Event| {
295 if event.method.as_str() != "element.added" {
296 return None;
297 }
298
299 let parsed = event.parse();
300 if let ParsedEvent::ElementAdded {
301 strategy,
302 value,
303 element_id,
304 ..
305 } = parsed
306 && strategy == expected_strategy
307 && value == expected_value
308 {
309 let element = Element::new(
310 ElementId::new(&*element_id),
311 tab_id,
312 frame_id,
313 session_id,
314 window_clone.clone(),
315 );
316 callback(element);
317 }
318
319 None
320 }),
321 );
322
323 let command = Command::Element(ElementCommand::Subscribe {
324 strategy: strategy_str,
325 value: value_str,
326 one_shot: false,
327 timeout: None,
328 });
329
330 let response = self.send_command(command).await?;
331
332 let subscription_id = response
333 .result
334 .as_ref()
335 .and_then(|v| v.get("subscriptionId"))
336 .and_then(|v| v.as_str())
337 .ok_or_else(|| Error::protocol("No subscriptionId in response"))?;
338
339 Ok(SubscriptionId::new(subscription_id))
340 }
341
342 pub async fn on_element_removed<F>(&self, element_id: &ElementId, callback: F) -> Result<()>
344 where
345 F: Fn() + Send + Sync + 'static,
346 {
347 debug!(tab_id = %self.inner.tab_id, %element_id, "Watching for element removal");
348
349 let window = self.get_window()?;
350
351 let element_id_clone = element_id.as_str().to_string();
352 let callback = Arc::new(callback);
353
354 let handler_key = format!("on_element_removed_{}", element_id_clone);
355
356 window.inner.pool.add_event_handler(
357 window.inner.session_id,
358 handler_key,
359 Box::new(move |event: Event| {
360 if event.method.as_str() != "element.removed" {
361 return None;
362 }
363
364 let parsed = event.parse();
365 if let ParsedEvent::ElementRemoved {
366 element_id: removed_id,
367 ..
368 } = parsed
369 && removed_id == element_id_clone
370 {
371 callback();
372 }
373
374 None
375 }),
376 );
377
378 let command = Command::Element(ElementCommand::WatchRemoval {
379 element_id: element_id.clone(),
380 });
381
382 self.send_command(command).await?;
383 Ok(())
384 }
385
386 pub async fn unsubscribe(&self, subscription_id: &SubscriptionId) -> Result<()> {
388 let command = Command::Element(ElementCommand::Unsubscribe {
389 subscription_id: subscription_id.as_str().to_string(),
390 });
391
392 self.send_command(command).await?;
393
394 if let Some(window) = &self.inner.window {
395 let key = format!("on_element_added_{}", subscription_id.as_str());
397 window
398 .inner
399 .pool
400 .remove_event_handler(window.inner.session_id, &key);
401 }
402
403 Ok(())
404 }
405}