1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
//! Frame tree traversal operations.
use std::sync::Arc;
use tracing::{debug, instrument};
use viewpoint_cdp::CdpConnection;
use viewpoint_cdp::protocol::runtime::ExecutionContextId;
use super::Frame;
use crate::error::PageError;
impl Frame {
/// Get child frames of this frame.
///
/// Returns a list of frames that are direct children of this frame.
///
/// # Errors
///
/// Returns an error if querying the frame tree fails.
#[instrument(level = "debug", skip(self), fields(frame_id = %self.id))]
pub async fn child_frames(&self) -> Result<Vec<Frame>, PageError> {
if self.is_detached() {
return Err(PageError::EvaluationFailed("Frame is detached".to_string()));
}
// Get the frame tree from CDP
let result: viewpoint_cdp::protocol::page::GetFrameTreeResult = self
.connection
.send_command("Page.getFrameTree", None::<()>, Some(&self.session_id))
.await?;
// Find this frame in the tree and return its children
let children = find_child_frames(
&result.frame_tree,
&self.id,
&self.connection,
&self.session_id,
);
Ok(children)
}
/// Get the parent frame.
///
/// Returns `None` if this is the main frame.
///
/// # Errors
///
/// Returns an error if querying the frame tree fails.
#[instrument(level = "debug", skip(self), fields(frame_id = %self.id))]
pub async fn parent_frame(&self) -> Result<Option<Frame>, PageError> {
if self.is_detached() {
return Err(PageError::EvaluationFailed("Frame is detached".to_string()));
}
// Main frame has no parent
if self.is_main() {
return Ok(None);
}
// Get the frame tree from CDP
let result: viewpoint_cdp::protocol::page::GetFrameTreeResult = self
.connection
.send_command("Page.getFrameTree", None::<()>, Some(&self.session_id))
.await?;
// Find the parent frame
let parent = find_parent_frame(
&result.frame_tree,
&self.id,
&self.connection,
&self.session_id,
);
Ok(parent)
}
/// Get or create an isolated world execution context for this frame.
///
/// Isolated worlds are separate JavaScript execution contexts that do not
/// share global scope with the main world or other isolated worlds.
/// They are useful for injecting scripts that should not interfere with
/// page scripts.
///
/// The world name is used to identify the isolated world. If an isolated
/// world with the same name already exists for this frame, its context ID
/// is returned. Otherwise, a new isolated world is created.
///
/// # Arguments
///
/// * `world_name` - A name for the isolated world (e.g., "viewpoint-isolated")
///
/// # Errors
///
/// Returns an error if:
/// - The frame is detached
/// - Creating the isolated world fails
#[instrument(level = "debug", skip(self), fields(frame_id = %self.id, world_name = %world_name))]
pub(crate) async fn get_or_create_isolated_world(
&self,
world_name: &str,
) -> Result<ExecutionContextId, PageError> {
if self.is_detached() {
return Err(PageError::EvaluationFailed("Frame is detached".to_string()));
}
// Check if we already have this isolated world cached
{
let data = self.data.read();
if let Some(&context_id) = data.execution_contexts.get(world_name) {
debug!(
context_id = context_id,
"Using cached isolated world context"
);
return Ok(context_id);
}
}
// Create a new isolated world
debug!("Creating new isolated world");
let result: viewpoint_cdp::protocol::page::CreateIsolatedWorldResult = self
.connection
.send_command(
"Page.createIsolatedWorld",
Some(viewpoint_cdp::protocol::page::CreateIsolatedWorldParams {
frame_id: self.id.clone(),
world_name: Some(world_name.to_string()),
grant_univeral_access: Some(true),
}),
Some(&self.session_id),
)
.await?;
let context_id = result.execution_context_id;
debug!(context_id = context_id, "Created isolated world");
// Cache the context ID
self.set_execution_context(world_name.to_string(), context_id);
Ok(context_id)
}
}
/// Recursively find child frames of a given frame ID.
pub(super) fn find_child_frames(
tree: &viewpoint_cdp::protocol::page::FrameTree,
parent_id: &str,
connection: &Arc<CdpConnection>,
session_id: &str,
) -> Vec<Frame> {
let mut children = Vec::new();
// Check if this is the parent we're looking for
if tree.frame.id == parent_id {
// Return all direct children
if let Some(ref child_frames) = tree.child_frames {
for child in child_frames {
children.push(Frame::new(
connection.clone(),
session_id.to_string(),
child.frame.id.clone(),
Some(parent_id.to_string()),
child.frame.loader_id.clone(),
child.frame.url.clone(),
child.frame.name.clone().unwrap_or_default(),
));
}
}
} else {
// Recurse into children to find the parent
if let Some(ref child_frames) = tree.child_frames {
for child in child_frames {
let found = find_child_frames(child, parent_id, connection, session_id);
children.extend(found);
}
}
}
children
}
/// Recursively find the parent frame of a given frame ID.
pub(super) fn find_parent_frame(
tree: &viewpoint_cdp::protocol::page::FrameTree,
frame_id: &str,
connection: &Arc<CdpConnection>,
session_id: &str,
) -> Option<Frame> {
// Check if any direct child is the frame we're looking for
if let Some(ref child_frames) = tree.child_frames {
for child in child_frames {
if child.frame.id == frame_id {
// Found it - return the current frame as the parent
return Some(Frame::new(
connection.clone(),
session_id.to_string(),
tree.frame.id.clone(),
tree.frame.parent_id.clone(),
tree.frame.loader_id.clone(),
tree.frame.url.clone(),
tree.frame.name.clone().unwrap_or_default(),
));
}
}
// Recurse into children
for child in child_frames {
if let Some(parent) = find_parent_frame(child, frame_id, connection, session_id) {
return Some(parent);
}
}
}
None
}