viewpoint_core/page/frame/
core.rs

1//! Core frame type and basic operations.
2
3use std::collections::HashMap;
4use std::sync::Arc;
5
6use parking_lot::RwLock;
7use viewpoint_cdp::CdpConnection;
8use viewpoint_cdp::protocol::runtime::ExecutionContextId;
9
10use super::execution_context::{ExecutionContextRegistry, MAIN_WORLD_KEY};
11
12/// Internal frame data that can be updated.
13#[derive(Debug, Clone)]
14pub(super) struct FrameData {
15    /// Frame's current URL.
16    pub url: String,
17    /// Frame's name attribute.
18    pub name: String,
19    /// Whether the frame is detached.
20    pub detached: bool,
21    /// Execution context IDs for this frame.
22    /// Key is the world name: "" for main world, other strings for isolated worlds.
23    pub execution_contexts: HashMap<String, ExecutionContextId>,
24}
25
26/// A frame within a page.
27///
28/// Frames are separate browsing contexts, typically created by `<iframe>` elements.
29/// Each frame has its own DOM and JavaScript execution context.
30#[derive(Debug)]
31pub struct Frame {
32    /// CDP connection.
33    pub(super) connection: Arc<CdpConnection>,
34    /// Session ID for this frame's page.
35    pub(super) session_id: String,
36    /// Unique frame identifier.
37    pub(super) id: String,
38    /// Parent frame ID (None for main frame).
39    pub(super) parent_id: Option<String>,
40    /// Loader ID for this frame.
41    pub(super) loader_id: String,
42    /// Mutable frame data.
43    pub(super) data: RwLock<FrameData>,
44    /// Execution context registry for looking up context IDs.
45    pub(super) context_registry: Option<Arc<ExecutionContextRegistry>>,
46    /// Context index for element ref generation (inherited from parent Page).
47    pub(super) context_index: usize,
48    /// Page index for element ref generation (inherited from parent Page).
49    pub(super) page_index: usize,
50    /// Frame index for element ref generation (0 = main frame, 1+ = child frames).
51    pub(super) frame_index: usize,
52}
53
54impl Frame {
55    /// Create a new frame from CDP frame info.
56    pub(crate) fn new(
57        connection: Arc<CdpConnection>,
58        session_id: String,
59        id: String,
60        parent_id: Option<String>,
61        loader_id: String,
62        url: String,
63        name: String,
64    ) -> Self {
65        Self {
66            connection,
67            session_id,
68            id,
69            parent_id,
70            loader_id,
71            data: RwLock::new(FrameData {
72                url,
73                name,
74                detached: false,
75                execution_contexts: HashMap::new(),
76            }),
77            context_registry: None,
78            context_index: 0,
79            page_index: 0,
80            frame_index: 0,
81        }
82    }
83
84    /// Create a new frame with indices for ref generation.
85    pub(crate) fn new_with_indices(
86        connection: Arc<CdpConnection>,
87        session_id: String,
88        id: String,
89        parent_id: Option<String>,
90        loader_id: String,
91        url: String,
92        name: String,
93        context_index: usize,
94        page_index: usize,
95        frame_index: usize,
96    ) -> Self {
97        Self {
98            connection,
99            session_id,
100            id,
101            parent_id,
102            loader_id,
103            data: RwLock::new(FrameData {
104                url,
105                name,
106                detached: false,
107                execution_contexts: HashMap::new(),
108            }),
109            context_registry: None,
110            context_index,
111            page_index,
112            frame_index,
113        }
114    }
115
116    /// Create a new frame with a context registry.
117    pub(crate) fn with_context_registry(
118        connection: Arc<CdpConnection>,
119        session_id: String,
120        id: String,
121        parent_id: Option<String>,
122        loader_id: String,
123        url: String,
124        name: String,
125        context_registry: Arc<ExecutionContextRegistry>,
126    ) -> Self {
127        Self {
128            connection,
129            session_id,
130            id,
131            parent_id,
132            loader_id,
133            data: RwLock::new(FrameData {
134                url,
135                name,
136                detached: false,
137                execution_contexts: HashMap::new(),
138            }),
139            context_registry: Some(context_registry),
140            context_index: 0,
141            page_index: 0,
142            frame_index: 0,
143        }
144    }
145
146    /// Create a new frame with context registry and indices.
147    pub(crate) fn with_context_registry_and_indices(
148        connection: Arc<CdpConnection>,
149        session_id: String,
150        id: String,
151        parent_id: Option<String>,
152        loader_id: String,
153        url: String,
154        name: String,
155        context_registry: Arc<ExecutionContextRegistry>,
156        context_index: usize,
157        page_index: usize,
158        frame_index: usize,
159    ) -> Self {
160        Self {
161            connection,
162            session_id,
163            id,
164            parent_id,
165            loader_id,
166            data: RwLock::new(FrameData {
167                url,
168                name,
169                detached: false,
170                execution_contexts: HashMap::new(),
171            }),
172            context_registry: Some(context_registry),
173            context_index,
174            page_index,
175            frame_index,
176        }
177    }
178
179    /// Get the frame index (0 = main frame, 1+ = child frames).
180    pub fn frame_index(&self) -> usize {
181        self.frame_index
182    }
183
184    /// Get the unique frame identifier.
185    pub fn id(&self) -> &str {
186        &self.id
187    }
188
189    /// Get the parent frame ID.
190    ///
191    /// Returns `None` for the main frame.
192    pub fn parent_id(&self) -> Option<&str> {
193        self.parent_id.as_deref()
194    }
195
196    /// Check if this is the main frame.
197    pub fn is_main(&self) -> bool {
198        self.parent_id.is_none()
199    }
200
201    /// Get the loader ID.
202    pub fn loader_id(&self) -> &str {
203        &self.loader_id
204    }
205
206    /// Get the frame's current URL.
207    pub fn url(&self) -> String {
208        self.data.read().url.clone()
209    }
210
211    /// Get the frame's name attribute.
212    pub fn name(&self) -> String {
213        self.data.read().name.clone()
214    }
215
216    /// Check if the frame has been detached.
217    pub fn is_detached(&self) -> bool {
218        self.data.read().detached
219    }
220
221    /// Update the frame's URL (called when frame navigates).
222    pub(crate) fn set_url(&self, url: String) {
223        self.data.write().url = url;
224    }
225
226    /// Update the frame's name.
227    pub(crate) fn set_name(&self, name: String) {
228        self.data.write().name = name;
229    }
230
231    /// Mark the frame as detached.
232    pub(crate) fn set_detached(&self) {
233        self.data.write().detached = true;
234    }
235
236    /// Get the main world execution context ID for this frame.
237    ///
238    /// First checks the shared context registry (if available), then falls back
239    /// to the local FrameData. Returns `None` if no context has been set.
240    pub(crate) fn main_world_context_id(&self) -> Option<ExecutionContextId> {
241        // First try the shared registry
242        if let Some(ref registry) = self.context_registry {
243            if let Some(context_id) = registry.main_world_context(&self.id) {
244                return Some(context_id);
245            }
246        }
247        // Fall back to local data (for backwards compatibility)
248        self.data
249            .read()
250            .execution_contexts
251            .get(MAIN_WORLD_KEY)
252            .copied()
253    }
254
255    /// Set an execution context for this frame.
256    ///
257    /// The world name should be:
258    /// - "" (empty string) for the main world
259    /// - A specific name for isolated worlds (e.g., "viewpoint-isolated")
260    pub(crate) fn set_execution_context(&self, world_name: String, id: ExecutionContextId) {
261        self.data.write().execution_contexts.insert(world_name, id);
262    }
263
264    /// Remove an execution context by its ID.
265    ///
266    /// Called when `Runtime.executionContextDestroyed` event is received.
267    /// Returns `true` if a context was removed.
268    pub(crate) fn remove_execution_context(&self, id: ExecutionContextId) -> bool {
269        let mut data = self.data.write();
270        let original_len = data.execution_contexts.len();
271        data.execution_contexts
272            .retain(|_, &mut ctx_id| ctx_id != id);
273        data.execution_contexts.len() < original_len
274    }
275
276    /// Clear all execution contexts.
277    ///
278    /// Called when frame navigates to a new document.
279    pub(crate) fn clear_execution_contexts(&self) {
280        self.data.write().execution_contexts.clear();
281    }
282
283    /// Get the session ID.
284    pub(crate) fn session_id(&self) -> &str {
285        &self.session_id
286    }
287
288    /// Get the connection.
289    pub(crate) fn connection(&self) -> &Arc<CdpConnection> {
290        &self.connection
291    }
292}