firefox_webdriver/browser/tab/
frames.rs

1//! Frame switching methods.
2
3use serde_json::Value;
4use tracing::debug;
5
6use crate::error::{Error, Result};
7use crate::identifiers::FrameId;
8use crate::protocol::{BrowsingContextCommand, Command, Response};
9
10use super::{FrameInfo, Tab};
11
12// ============================================================================
13// Tab - Frame Switching
14// ============================================================================
15
16impl Tab {
17    /// Switches to a frame by iframe element.
18    ///
19    /// Returns a new Tab handle with the updated frame context.
20    ///
21    /// # Arguments
22    ///
23    /// * `iframe` - Element reference to an iframe
24    ///
25    /// # Example
26    ///
27    /// ```ignore
28    /// let iframe = tab.find_element("iframe#content").await?;
29    /// let frame_tab = tab.switch_to_frame(&iframe).await?;
30    /// ```
31    pub async fn switch_to_frame(&self, iframe: &crate::browser::Element) -> Result<Tab> {
32        debug!(tab_id = %self.inner.tab_id, element_id = %iframe.id(), "Switching to frame");
33
34        let command = Command::BrowsingContext(BrowsingContextCommand::SwitchToFrame {
35            element_id: iframe.id().clone(),
36        });
37        let response = self.send_command(command).await?;
38
39        let frame_id = extract_frame_id(&response)?;
40
41        Ok(Tab::new(
42            self.inner.tab_id,
43            FrameId::new(frame_id),
44            self.inner.session_id,
45            self.inner.window.clone(),
46        ))
47    }
48
49    /// Switches to a frame by index (0-based).
50    ///
51    /// # Arguments
52    ///
53    /// * `index` - Zero-based index of the frame
54    pub async fn switch_to_frame_by_index(&self, index: usize) -> Result<Tab> {
55        debug!(tab_id = %self.inner.tab_id, index, "Switching to frame by index");
56
57        let command =
58            Command::BrowsingContext(BrowsingContextCommand::SwitchToFrameByIndex { index });
59        let response = self.send_command(command).await?;
60
61        let frame_id = extract_frame_id(&response)?;
62
63        Ok(Tab::new(
64            self.inner.tab_id,
65            FrameId::new(frame_id),
66            self.inner.session_id,
67            self.inner.window.clone(),
68        ))
69    }
70
71    /// Switches to a frame by URL pattern.
72    ///
73    /// Supports wildcards (`*` for any characters, `?` for single character).
74    ///
75    /// # Arguments
76    ///
77    /// * `url_pattern` - URL pattern with optional wildcards
78    pub async fn switch_to_frame_by_url(&self, url_pattern: &str) -> Result<Tab> {
79        debug!(tab_id = %self.inner.tab_id, url_pattern, "Switching to frame by URL");
80
81        let command = Command::BrowsingContext(BrowsingContextCommand::SwitchToFrameByUrl {
82            url_pattern: url_pattern.to_string(),
83        });
84        let response = self.send_command(command).await?;
85
86        let frame_id = extract_frame_id(&response)?;
87
88        Ok(Tab::new(
89            self.inner.tab_id,
90            FrameId::new(frame_id),
91            self.inner.session_id,
92            self.inner.window.clone(),
93        ))
94    }
95
96    /// Switches to the parent frame.
97    pub async fn switch_to_parent_frame(&self) -> Result<Tab> {
98        debug!(tab_id = %self.inner.tab_id, "Switching to parent frame");
99
100        let command = Command::BrowsingContext(BrowsingContextCommand::SwitchToParentFrame);
101        let response = self.send_command(command).await?;
102
103        let frame_id = extract_frame_id(&response)?;
104
105        Ok(Tab::new(
106            self.inner.tab_id,
107            FrameId::new(frame_id),
108            self.inner.session_id,
109            self.inner.window.clone(),
110        ))
111    }
112
113    /// Switches to the main (top-level) frame.
114    #[must_use]
115    pub fn switch_to_main_frame(&self) -> Tab {
116        debug!(tab_id = %self.inner.tab_id, "Switching to main frame");
117
118        Tab::new(
119            self.inner.tab_id,
120            FrameId::main(),
121            self.inner.session_id,
122            self.inner.window.clone(),
123        )
124    }
125
126    /// Gets the count of direct child frames.
127    pub async fn get_frame_count(&self) -> Result<usize> {
128        debug!(tab_id = %self.inner.tab_id, "Getting frame count");
129        let command = Command::BrowsingContext(BrowsingContextCommand::GetFrameCount);
130        let response = self.send_command(command).await?;
131
132        let count = response
133            .result
134            .as_ref()
135            .and_then(|v| v.get("count"))
136            .and_then(|v| v.as_u64())
137            .ok_or_else(|| Error::protocol("No count in response"))?;
138
139        debug!(tab_id = %self.inner.tab_id, count = count, "Got frame count");
140        Ok(count as usize)
141    }
142
143    /// Gets information about all frames in the tab.
144    pub async fn get_all_frames(&self) -> Result<Vec<FrameInfo>> {
145        debug!(tab_id = %self.inner.tab_id, "Getting all frames");
146        let command = Command::BrowsingContext(BrowsingContextCommand::GetAllFrames);
147        let response = self.send_command(command).await?;
148
149        let frames: Vec<FrameInfo> = response
150            .result
151            .as_ref()
152            .and_then(|v| v.get("frames"))
153            .and_then(|v| v.as_array())
154            .map(|arr| arr.iter().filter_map(parse_frame_info).collect())
155            .unwrap_or_default();
156
157        debug!(tab_id = %self.inner.tab_id, count = frames.len(), "Got all frames");
158        Ok(frames)
159    }
160}
161
162// ============================================================================
163// Helper Functions
164// ============================================================================
165
166/// Extracts frame ID from response.
167fn extract_frame_id(response: &Response) -> Result<u64> {
168    response
169        .result
170        .as_ref()
171        .and_then(|v| v.get("frameId"))
172        .and_then(|v| v.as_u64())
173        .ok_or_else(|| Error::protocol("No frameId in response"))
174}
175
176/// Parses frame info from JSON value.
177fn parse_frame_info(v: &Value) -> Option<FrameInfo> {
178    Some(FrameInfo {
179        frame_id: FrameId::new(v.get("frameId")?.as_u64()?),
180        parent_frame_id: v
181            .get("parentFrameId")
182            .and_then(|p| p.as_i64())
183            .and_then(|p| {
184                if p < 0 {
185                    None
186                } else {
187                    Some(FrameId::new(p as u64))
188                }
189            }),
190        url: v.get("url")?.as_str()?.to_string(),
191    })
192}