Skip to main content

browser_commander/utilities/
url.rs

1//! URL utilities for browser automation.
2//!
3//! This module provides utilities for working with URLs.
4
5use crate::core::engine::{EngineAdapter, EngineError};
6
7/// Get the current URL from the page.
8///
9/// # Arguments
10///
11/// * `adapter` - The engine adapter to use
12///
13/// # Returns
14///
15/// The current URL
16pub async fn get_url(adapter: &dyn EngineAdapter) -> Result<String, EngineError> {
17    adapter.url().await
18}
19
20/// Unfocus the address bar (useful after browser launch).
21///
22/// This helps prevent accidental typing into the address bar.
23///
24/// # Arguments
25///
26/// * `adapter` - The engine adapter to use
27///
28/// # Returns
29///
30/// Ok if successful (errors are silently ignored as this is a UX improvement)
31pub async fn unfocus_address_bar(adapter: &dyn EngineAdapter) -> Result<(), EngineError> {
32    // Bring page to front - this removes focus from address bar
33    match adapter.bring_to_front().await {
34        Ok(_) => Ok(()),
35        Err(_) => Ok(()), // Ignore errors - this is just a UX improvement
36    }
37}
38
39/// Parse and normalize a URL.
40///
41/// # Arguments
42///
43/// * `url_str` - The URL string to parse
44///
45/// # Returns
46///
47/// The parsed URL or an error
48pub fn parse_url(url_str: &str) -> Result<url::Url, url::ParseError> {
49    url::Url::parse(url_str)
50}
51
52/// Check if two URLs are the same origin.
53///
54/// # Arguments
55///
56/// * `url1` - First URL
57/// * `url2` - Second URL
58///
59/// # Returns
60///
61/// `true` if both URLs have the same origin
62pub fn same_origin(url1: &str, url2: &str) -> bool {
63    match (parse_url(url1), parse_url(url2)) {
64        (Ok(u1), Ok(u2)) => u1.origin() == u2.origin(),
65        _ => false,
66    }
67}
68
69/// Extract the domain from a URL.
70///
71/// # Arguments
72///
73/// * `url_str` - The URL string
74///
75/// # Returns
76///
77/// The domain/host if available
78pub fn get_domain(url_str: &str) -> Option<String> {
79    parse_url(url_str)
80        .ok()
81        .and_then(|u| u.host_str().map(String::from))
82}
83
84/// Check if a URL is a data URL.
85///
86/// # Arguments
87///
88/// * `url_str` - The URL string
89///
90/// # Returns
91///
92/// `true` if this is a data URL
93pub fn is_data_url(url_str: &str) -> bool {
94    url_str.starts_with("data:")
95}
96
97/// Check if a URL is a blob URL.
98///
99/// # Arguments
100///
101/// * `url_str` - The URL string
102///
103/// # Returns
104///
105/// `true` if this is a blob URL
106pub fn is_blob_url(url_str: &str) -> bool {
107    url_str.starts_with("blob:")
108}
109
110/// Check if a URL is an about: URL.
111///
112/// # Arguments
113///
114/// * `url_str` - The URL string
115///
116/// # Returns
117///
118/// `true` if this is an about: URL
119pub fn is_about_url(url_str: &str) -> bool {
120    url_str.starts_with("about:")
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn parse_url_valid() {
129        let url = parse_url("https://example.com/path?query=value").unwrap();
130        assert_eq!(url.scheme(), "https");
131        assert_eq!(url.host_str(), Some("example.com"));
132        assert_eq!(url.path(), "/path");
133        assert_eq!(url.query(), Some("query=value"));
134    }
135
136    #[test]
137    fn parse_url_invalid() {
138        let result = parse_url("not a url");
139        assert!(result.is_err());
140    }
141
142    #[test]
143    fn same_origin_true() {
144        assert!(same_origin(
145            "https://example.com/page1",
146            "https://example.com/page2"
147        ));
148        assert!(same_origin(
149            "https://example.com:443/path",
150            "https://example.com/"
151        ));
152    }
153
154    #[test]
155    fn same_origin_false() {
156        assert!(!same_origin("https://example.com/", "https://other.com/"));
157        assert!(!same_origin("https://example.com/", "http://example.com/"));
158        assert!(!same_origin(
159            "https://example.com/",
160            "https://sub.example.com/"
161        ));
162    }
163
164    #[test]
165    fn get_domain_valid() {
166        assert_eq!(
167            get_domain("https://example.com/path"),
168            Some("example.com".to_string())
169        );
170        assert_eq!(
171            get_domain("https://sub.example.com/"),
172            Some("sub.example.com".to_string())
173        );
174    }
175
176    #[test]
177    fn get_domain_invalid() {
178        assert_eq!(get_domain("not a url"), None);
179        assert_eq!(get_domain("data:text/plain,hello"), None);
180    }
181
182    #[test]
183    fn is_data_url_true() {
184        assert!(is_data_url("data:text/plain,hello"));
185        assert!(is_data_url("data:image/png;base64,abc123"));
186    }
187
188    #[test]
189    fn is_data_url_false() {
190        assert!(!is_data_url("https://example.com"));
191        assert!(!is_data_url("blob:https://example.com/abc"));
192    }
193
194    #[test]
195    fn is_blob_url_true() {
196        assert!(is_blob_url("blob:https://example.com/abc-123"));
197    }
198
199    #[test]
200    fn is_blob_url_false() {
201        assert!(!is_blob_url("https://example.com"));
202        assert!(!is_blob_url("data:text/plain,hello"));
203    }
204
205    #[test]
206    fn is_about_url_true() {
207        assert!(is_about_url("about:blank"));
208        assert!(is_about_url("about:srcdoc"));
209    }
210
211    #[test]
212    fn is_about_url_false() {
213        assert!(!is_about_url("https://example.com"));
214        assert!(!is_about_url("data:text/plain,hello"));
215    }
216}