glass/browser/session/
navigate.rs1use super::*;
7
8impl BrowserSession {
9 pub async fn page_info(&self) -> BrowserResult<PageInfo> {
14 self.cdp.with_current_route(async {
15 let raw = self
16 .cdp
17 .evaluate(
18 "JSON.stringify({url: location.href, title: document.title, ready_state: document.readyState})",
19 )
20 .await?;
21 let value = runtime_value(&raw)?;
22 let json = value
23 .as_str()
24 .ok_or("document state evaluation returned a non-string value")?;
25 let mut page: PageInfo = serde_json::from_str(json)?;
26 (page.target_id, page.frame_id) = self.route_identity().await?;
27 Ok(page)
28 }).await
29 }
30
31 pub async fn navigate(&self, url: &str) -> BrowserResult<PageInfo> {
36 self.navigate_with_deadline(url, Duration::from_secs(20))
37 .await
38 }
39
40 pub async fn navigate_with_deadline(
45 &self,
46 url: &str,
47 deadline: Duration,
48 ) -> BrowserResult<PageInfo> {
49 self.cdp
50 .with_current_target_route(async {
51 validate_wait_deadline(deadline)?;
52 let url = normalize_url(url);
53 self.policy.require_url(&url).await?;
54 self.enforce_polite_navigation(&url).await?;
55 if let Some(interception) = &self.policy_interception
56 && let Some(error) = interception.take_denial().await
57 {
58 return Err(error.into());
59 }
60 let result = async {
61 let mut events = self.cdp.subscribe_events();
62 let started = tokio::time::Instant::now();
63 let navigation = tokio::time::timeout(deadline, self.cdp.navigate(&url))
64 .await
65 .map_err(|_| {
66 wait_timeout("lifecycle", deadline, "navigate_command_pending")
67 })??;
68 if let Some(frame_id) = navigation.frame_id.as_deref() {
69 validate_topology_id(frame_id)?;
70 self.topology.lock().await.active_frame_id = Some(frame_id.to_string());
71 self.cdp
72 .set_active_frame_context(Some(frame_id.to_string()), None);
73 }
74 let remaining = deadline.saturating_sub(started.elapsed());
75 self.wait_loop(
76 WaitCondition::Lifecycle("complete".to_string()),
77 remaining,
78 deadline,
79 &mut events,
80 true,
81 )
82 .await?;
83 let remaining = deadline.saturating_sub(started.elapsed());
84 let main_frame = self
85 .list_frames()
86 .await?
87 .into_iter()
88 .find(|frame| frame.parent_id.is_none())
89 .ok_or("navigated target returned no main frame")?;
90 self.select_frame(&main_frame.id).await?;
91 let page = tokio::time::timeout(remaining, self.page_info())
92 .await
93 .map_err(|_| wait_timeout("lifecycle", deadline, "page_info_pending"))??;
94 self.invalidate_observation();
95 self.record_audit("navigate", url);
96 Ok(page)
97 }
98 .await;
99 if let Some(error) = match &self.policy_interception {
100 Some(interception) => interception.take_denial().await,
101 None => None,
102 } {
103 return Err(error.into());
104 }
105 result
106 })
107 .await
108 }
109
110 pub async fn navigate_with_revision(
114 &self,
115 url: &str,
116 deadline: Duration,
117 expected_revision: u64,
118 ) -> BrowserResult<NavigationOutcome> {
119 self.require_expected_revision(Some(expected_revision))?;
120 let previous_revision = self.page_revision.load(Ordering::Relaxed);
121 let before = self.page_info().await.ok();
122 let page = self.navigate_with_deadline(url, deadline).await?;
123 let current_revision = self.page_revision.load(Ordering::Relaxed);
124 Ok(NavigationOutcome {
125 status: ActionStatus::Succeeded,
126 action: ActionKind::Navigate,
127 execution_id: self.next_execution_id(),
128 verification: ActionVerificationEvidence {
129 revision_delta: current_revision.saturating_sub(previous_revision),
130 url_changed: before.as_ref().is_some_and(|before| before.url != page.url),
131 title_changed: before
132 .as_ref()
133 .is_some_and(|before| before.title != page.title),
134 target_changed: before
135 .as_ref()
136 .is_some_and(|before| before.target_id != page.target_id),
137 frame_changed: before
138 .as_ref()
139 .is_some_and(|before| before.frame_id != page.frame_id),
140 ..ActionVerificationEvidence::default()
141 },
142 page,
143 previous_revision,
144 current_revision,
145 })
146 }
147}