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}
47
48impl Frame {
49    /// Create a new frame from CDP frame info.
50    pub(crate) fn new(
51        connection: Arc<CdpConnection>,
52        session_id: String,
53        id: String,
54        parent_id: Option<String>,
55        loader_id: String,
56        url: String,
57        name: String,
58    ) -> Self {
59        Self {
60            connection,
61            session_id,
62            id,
63            parent_id,
64            loader_id,
65            data: RwLock::new(FrameData {
66                url,
67                name,
68                detached: false,
69                execution_contexts: HashMap::new(),
70            }),
71            context_registry: None,
72        }
73    }
74
75    /// Create a new frame with a context registry.
76    pub(crate) fn with_context_registry(
77        connection: Arc<CdpConnection>,
78        session_id: String,
79        id: String,
80        parent_id: Option<String>,
81        loader_id: String,
82        url: String,
83        name: String,
84        context_registry: Arc<ExecutionContextRegistry>,
85    ) -> Self {
86        Self {
87            connection,
88            session_id,
89            id,
90            parent_id,
91            loader_id,
92            data: RwLock::new(FrameData {
93                url,
94                name,
95                detached: false,
96                execution_contexts: HashMap::new(),
97            }),
98            context_registry: Some(context_registry),
99        }
100    }
101
102    /// Get the unique frame identifier.
103    pub fn id(&self) -> &str {
104        &self.id
105    }
106
107    /// Get the parent frame ID.
108    ///
109    /// Returns `None` for the main frame.
110    pub fn parent_id(&self) -> Option<&str> {
111        self.parent_id.as_deref()
112    }
113
114    /// Check if this is the main frame.
115    pub fn is_main(&self) -> bool {
116        self.parent_id.is_none()
117    }
118
119    /// Get the loader ID.
120    pub fn loader_id(&self) -> &str {
121        &self.loader_id
122    }
123
124    /// Get the frame's current URL.
125    pub fn url(&self) -> String {
126        self.data.read().url.clone()
127    }
128
129    /// Get the frame's name attribute.
130    pub fn name(&self) -> String {
131        self.data.read().name.clone()
132    }
133
134    /// Check if the frame has been detached.
135    pub fn is_detached(&self) -> bool {
136        self.data.read().detached
137    }
138
139    /// Update the frame's URL (called when frame navigates).
140    pub(crate) fn set_url(&self, url: String) {
141        self.data.write().url = url;
142    }
143
144    /// Update the frame's name.
145    pub(crate) fn set_name(&self, name: String) {
146        self.data.write().name = name;
147    }
148
149    /// Mark the frame as detached.
150    pub(crate) fn set_detached(&self) {
151        self.data.write().detached = true;
152    }
153
154    /// Get the main world execution context ID for this frame.
155    ///
156    /// First checks the shared context registry (if available), then falls back
157    /// to the local FrameData. Returns `None` if no context has been set.
158    pub(crate) fn main_world_context_id(&self) -> Option<ExecutionContextId> {
159        // First try the shared registry
160        if let Some(ref registry) = self.context_registry {
161            if let Some(context_id) = registry.main_world_context(&self.id) {
162                return Some(context_id);
163            }
164        }
165        // Fall back to local data (for backwards compatibility)
166        self.data.read().execution_contexts.get(MAIN_WORLD_KEY).copied()
167    }
168
169    /// Set an execution context for this frame.
170    ///
171    /// The world name should be:
172    /// - "" (empty string) for the main world
173    /// - A specific name for isolated worlds (e.g., "viewpoint-isolated")
174    pub(crate) fn set_execution_context(&self, world_name: String, id: ExecutionContextId) {
175        self.data.write().execution_contexts.insert(world_name, id);
176    }
177
178    /// Remove an execution context by its ID.
179    ///
180    /// Called when `Runtime.executionContextDestroyed` event is received.
181    /// Returns `true` if a context was removed.
182    pub(crate) fn remove_execution_context(&self, id: ExecutionContextId) -> bool {
183        let mut data = self.data.write();
184        let original_len = data.execution_contexts.len();
185        data.execution_contexts.retain(|_, &mut ctx_id| ctx_id != id);
186        data.execution_contexts.len() < original_len
187    }
188
189    /// Clear all execution contexts.
190    ///
191    /// Called when frame navigates to a new document.
192    pub(crate) fn clear_execution_contexts(&self) {
193        self.data.write().execution_contexts.clear();
194    }
195
196    /// Get the session ID.
197    pub(crate) fn session_id(&self) -> &str {
198        &self.session_id
199    }
200
201    /// Get the connection.
202    pub(crate) fn connection(&self) -> &Arc<CdpConnection> {
203        &self.connection
204    }
205}