playwright_core/
channel_owner.rs

1// Copyright 2024 Paul Adamson
2// Licensed under the Apache License, Version 2.0
3//
4// Channel Owner - Base trait for all Playwright protocol objects
5//
6// Architecture Reference:
7// - Python: playwright-python/playwright/_impl/_connection.py (ChannelOwner class)
8// - Java: playwright-java/.../impl/ChannelOwner.java
9// - JavaScript: playwright/.../client/channelOwner.ts
10//
11// All Playwright objects (Browser, Page, etc.) implement ChannelOwner to:
12// - Represent remote objects on the server via GUID
13// - Participate in parent-child lifecycle management
14// - Handle protocol events
15// - Communicate via Channel proxy
16
17use crate::channel::Channel;
18use crate::connection::ConnectionLike;
19use parking_lot::Mutex;
20use serde_json::Value;
21use std::any::Any;
22use std::collections::HashMap;
23use std::sync::atomic::{AtomicBool, Ordering};
24use std::sync::{Arc, Weak};
25
26/// Type alias for the children registry mapping GUIDs to ChannelOwner objects
27type ChildrenRegistry = HashMap<Arc<str>, Arc<dyn ChannelOwner>>;
28
29/// Reason why an object was disposed
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum DisposeReason {
32    /// Object was explicitly closed by user code
33    Closed,
34    /// Object was garbage collected by the server
35    GarbageCollected,
36}
37
38/// Parent can be either another ChannelOwner or the root Connection
39pub enum ParentOrConnection {
40    Parent(Arc<dyn ChannelOwner>),
41    Connection(Arc<dyn ConnectionLike>),
42}
43
44/// Base trait for all Playwright protocol objects.
45///
46/// Every object in the Playwright protocol (Browser, Page, BrowserContext, etc.)
47/// implements this trait to enable:
48/// - GUID-based object identity and lookup
49/// - Hierarchical parent-child lifecycle management
50/// - Channel-based RPC communication
51/// - Protocol event handling
52///
53/// # Architecture
54///
55/// All official Playwright bindings (Python, Java, .NET) follow this pattern:
56///
57/// 1. **GUID Identity**: Each object has a unique GUID from the server
58/// 2. **Parent-Child Tree**: Objects form a hierarchy (e.g., Browser → BrowserContext → Page)
59/// 3. **Dual Registry**: Objects are registered in both connection (global) and parent (lifecycle)
60/// 4. **Channel Communication**: Objects send/receive messages via their Channel
61/// 5. **Event Handling**: Protocol events are dispatched to objects by GUID
62///
63/// # Example
64///
65/// ```ignore
66/// # use playwright_core::channel_owner::ChannelOwner;
67/// # use std::sync::Arc;
68/// # fn example(browser: Arc<dyn ChannelOwner>) {
69/// // Get object identity
70/// println!("Object GUID: {}", browser.guid());
71/// println!("Object type: {}", browser.type_name());
72///
73/// // Handle lifecycle
74/// browser.dispose(playwright_core::channel_owner::DisposeReason::Closed);
75/// # }
76/// ```
77pub trait ChannelOwner: Send + Sync {
78    /// Returns the unique GUID for this object.
79    ///
80    /// The GUID is assigned by the Playwright server and used for:
81    /// - Looking up objects in the connection registry
82    /// - Routing protocol messages to the correct object
83    /// - Parent-child relationship tracking
84    fn guid(&self) -> &str;
85
86    /// Returns the protocol type name (e.g., "Browser", "Page").
87    fn type_name(&self) -> &str;
88
89    /// Returns the parent object, if any.
90    ///
91    /// The root Playwright object has no parent.
92    fn parent(&self) -> Option<Arc<dyn ChannelOwner>>;
93
94    /// Returns the connection this object belongs to.
95    fn connection(&self) -> Arc<dyn ConnectionLike>;
96
97    /// Returns the raw initializer JSON from the server.
98    ///
99    /// The initializer contains the object's initial state sent
100    /// in the `__create__` protocol message.
101    fn initializer(&self) -> &Value;
102
103    /// Returns the channel for RPC communication.
104    fn channel(&self) -> &Channel;
105
106    /// Disposes this object and all its children.
107    ///
108    /// Called when:
109    /// - Server sends `__dispose__` message
110    /// - User explicitly closes the object
111    /// - Parent is disposed (cascades to children)
112    ///
113    /// # Arguments
114    /// * `reason` - Why the object is being disposed
115    fn dispose(&self, reason: DisposeReason);
116
117    /// Adopts a child object (moves from old parent to this parent).
118    ///
119    /// Called when server sends `__adopt__` message, typically when:
120    /// - A page is moved between browser contexts
121    /// - An object's ownership changes
122    fn adopt(&self, child: Arc<dyn ChannelOwner>);
123
124    /// Adds a child object to this parent's registry.
125    ///
126    /// Called during object creation and adoption.
127    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>);
128
129    /// Removes a child object from this parent's registry.
130    ///
131    /// Called during disposal and adoption.
132    fn remove_child(&self, guid: &str);
133
134    /// Handles a protocol event sent to this object.
135    ///
136    /// # Arguments
137    /// * `method` - Event name (e.g., "close", "load")
138    /// * `params` - Event parameters as JSON
139    fn on_event(&self, method: &str, params: Value);
140
141    /// Returns true if this object was garbage collected.
142    fn was_collected(&self) -> bool;
143
144    /// Enables downcasting to concrete types.
145    ///
146    /// Required for converting `Arc<dyn ChannelOwner>` to specific types
147    /// like `Arc<Browser>` when retrieving objects from the connection.
148    fn as_any(&self) -> &dyn Any;
149}
150
151/// Base implementation of ChannelOwner that can be embedded in protocol objects.
152///
153/// This struct provides the common functionality for all ChannelOwner implementations.
154/// Protocol objects (Browser, Page, etc.) should contain this as a field and
155/// delegate trait methods to it.
156///
157/// # Example
158///
159/// ```ignore
160/// use playwright_core::channel_owner::{ChannelOwner, ChannelOwnerImpl, ParentOrConnection, DisposeReason};
161/// use playwright_core::channel::Channel;
162/// use playwright_core::connection::ConnectionLike;
163/// use std::sync::Arc;
164/// use std::any::Any;
165/// use serde_json::Value;
166///
167/// pub struct Browser {
168///     base: ChannelOwnerImpl,
169///     // ... browser-specific fields
170/// }
171///
172/// impl Browser {
173///     pub fn new(
174///         parent: Arc<dyn ChannelOwner>,
175///         type_name: String,
176///         guid: Arc<str>,
177///         initializer: Value,
178///     ) -> Self {
179///         let base = ChannelOwnerImpl::new(
180///             ParentOrConnection::Parent(parent),
181///             type_name,
182///             guid,
183///             initializer,
184///         );
185///         Self { base }
186///     }
187/// }
188///
189/// impl ChannelOwner for Browser {
190///     fn guid(&self) -> &str { self.base.guid() }
191///     fn type_name(&self) -> &str { self.base.type_name() }
192///     fn parent(&self) -> Option<Arc<dyn ChannelOwner>> { self.base.parent() }
193///     fn connection(&self) -> Arc<dyn ConnectionLike> { self.base.connection() }
194///     fn initializer(&self) -> &Value { self.base.initializer() }
195///     fn channel(&self) -> &Channel { self.base.channel() }
196///     fn dispose(&self, reason: DisposeReason) { self.base.dispose(reason) }
197///     fn adopt(&self, child: Arc<dyn ChannelOwner>) { self.base.adopt(child) }
198///     fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
199///         self.base.add_child(guid, child)
200///     }
201///     fn remove_child(&self, guid: &str) { self.base.remove_child(guid) }
202///     fn on_event(&self, method: &str, params: Value) { self.base.on_event(method, params) }
203///     fn was_collected(&self) -> bool { self.base.was_collected() }
204///     fn as_any(&self) -> &dyn Any { self }
205/// }
206/// ```
207pub struct ChannelOwnerImpl {
208    guid: Arc<str>,
209    type_name: String,
210    parent: Option<Weak<dyn ChannelOwner>>,
211    connection: Arc<dyn ConnectionLike>,
212    children: Arc<Mutex<ChildrenRegistry>>,
213    channel: Channel,
214    initializer: Value,
215    was_collected: AtomicBool,
216}
217
218impl Clone for ChannelOwnerImpl {
219    fn clone(&self) -> Self {
220        Self {
221            guid: self.guid.clone(),
222            type_name: self.type_name.clone(),
223            parent: self.parent.clone(),
224            connection: Arc::clone(&self.connection),
225            children: Arc::clone(&self.children),
226            channel: self.channel.clone(),
227            initializer: self.initializer.clone(),
228            was_collected: AtomicBool::new(
229                self.was_collected.load(std::sync::atomic::Ordering::SeqCst),
230            ),
231        }
232    }
233}
234
235impl ChannelOwnerImpl {
236    /// Creates a new ChannelOwner base implementation.
237    ///
238    /// This constructor:
239    /// 1. Extracts the connection from parent or uses provided connection
240    /// 2. Creates the channel for RPC communication
241    /// 3. Stores the initializer data
242    /// 4. Registers itself in the connection (done by caller via Connection::register_object)
243    /// 5. Registers itself in parent (done by caller via parent.add_child)
244    ///
245    /// # Arguments
246    /// * `parent` - Either a parent ChannelOwner or the root Connection
247    /// * `type_name` - Protocol type name (e.g., "Browser")
248    /// * `guid` - Unique GUID from server
249    /// * `initializer` - Initial state from `__create__` message
250    pub fn new(
251        parent: ParentOrConnection,
252        type_name: String,
253        guid: Arc<str>,
254        initializer: Value,
255    ) -> Self {
256        let (connection, parent_opt) = match parent {
257            ParentOrConnection::Parent(p) => {
258                let conn = p.connection();
259                (conn, Some(Arc::downgrade(&p)))
260            }
261            ParentOrConnection::Connection(c) => (c, None),
262        };
263
264        // Arc<str> allows efficient sharing of GUID without cloning
265        let channel = Channel::new(Arc::clone(&guid), connection.clone());
266
267        Self {
268            guid,
269            type_name,
270            parent: parent_opt,
271            connection,
272            children: Arc::new(Mutex::new(HashMap::new())),
273            channel,
274            initializer,
275            was_collected: AtomicBool::new(false),
276        }
277    }
278
279    /// Returns the unique GUID for this object.
280    pub fn guid(&self) -> &str {
281        &self.guid
282    }
283
284    /// Returns the protocol type name.
285    pub fn type_name(&self) -> &str {
286        &self.type_name
287    }
288
289    /// Returns the parent object, if any.
290    pub fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
291        self.parent.as_ref().and_then(|p| p.upgrade())
292    }
293
294    /// Returns the connection.
295    pub fn connection(&self) -> Arc<dyn ConnectionLike> {
296        self.connection.clone()
297    }
298
299    /// Returns the initializer JSON.
300    pub fn initializer(&self) -> &Value {
301        &self.initializer
302    }
303
304    /// Returns the channel for RPC.
305    pub fn channel(&self) -> &Channel {
306        &self.channel
307    }
308
309    /// Disposes this object and all children recursively.
310    ///
311    /// # Arguments
312    /// * `reason` - Why the object is being disposed
313    pub fn dispose(&self, reason: DisposeReason) {
314        // Mark as collected if garbage collected
315        if reason == DisposeReason::GarbageCollected {
316            self.was_collected.store(true, Ordering::SeqCst);
317        }
318
319        // Remove from parent
320        if let Some(parent) = self.parent() {
321            parent.remove_child(&self.guid);
322        }
323
324        // Remove from connection (spawn to avoid blocking in sync context)
325        let connection = self.connection.clone();
326        let guid = Arc::clone(&self.guid);
327        tokio::spawn(async move {
328            connection.unregister_object(&guid).await;
329        });
330
331        // Dispose all children (snapshot to avoid holding lock)
332        let children: Vec<_> = {
333            let guard = self.children.lock();
334            guard.values().cloned().collect()
335        };
336
337        for child in children {
338            child.dispose(reason);
339        }
340
341        // Clear children
342        self.children.lock().clear();
343    }
344
345    /// Adopts a child object (moves from old parent to this parent).
346    pub fn adopt(&self, child: Arc<dyn ChannelOwner>) {
347        // Remove from old parent
348        if let Some(old_parent) = child.parent() {
349            old_parent.remove_child(child.guid());
350        }
351
352        // Add to this parent
353        // Convert &str to Arc<str> for the hashmap key
354        self.add_child(Arc::from(child.guid()), child);
355    }
356
357    /// Adds a child to this parent's registry.
358    pub fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
359        self.children.lock().insert(guid, child);
360    }
361
362    /// Removes a child from this parent's registry.
363    pub fn remove_child(&self, guid: &str) {
364        // Create Arc<str> for lookup
365        let guid_arc: Arc<str> = Arc::from(guid);
366        self.children.lock().remove(&guid_arc);
367    }
368
369    /// Handles a protocol event (default implementation logs it).
370    ///
371    /// Subclasses should override this to handle specific events.
372    pub fn on_event(&self, method: &str, params: Value) {
373        tracing::debug!(
374            "Event on {} ({}): {} -> {:?}",
375            self.guid,
376            self.type_name,
377            method,
378            params
379        );
380    }
381
382    /// Returns true if object was garbage collected.
383    pub fn was_collected(&self) -> bool {
384        self.was_collected.load(Ordering::SeqCst)
385    }
386}
387
388// Note: ChannelOwner testing is done via integration tests since it requires:
389// - Real Connection with object registry
390// - Multiple connected objects (parent-child relationships)
391// - Protocol messages from the server
392// See: crates/playwright-core/tests/connection_integration.rs