Skip to main content

playwright_rs/protocol/
worker.rs

1// Worker — Web Worker and Service Worker
2
3use crate::error::Result;
4use crate::protocol::evaluate_conversion::{parse_result, serialize_argument, serialize_null};
5use crate::server::channel::Channel;
6use crate::server::channel_owner::{ChannelOwner, ChannelOwnerImpl, ParentOrConnection};
7use crate::server::connection::ConnectionExt;
8use serde::de::DeserializeOwned;
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use std::any::Any;
12use std::sync::Arc;
13
14/// Worker represents a Web Worker or Service Worker.
15///
16/// Workers are created by the page using the `Worker` constructor or by browsers
17/// for registered service workers. They run JS in an isolated global scope.
18///
19/// # Example
20///
21/// ```ignore
22/// use playwright_rs::protocol::Playwright;
23///
24/// #[tokio::main]
25/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
26///     let playwright = Playwright::launch().await?;
27///     let browser = playwright.chromium().launch().await?;
28///     let page = browser.new_page().await?;
29///
30///     page.on_worker(|worker| {
31///         println!("Worker created: {}", worker.url());
32///         Box::pin(async move { Ok(()) })
33///     }).await?;
34///
35///     browser.close().await?;
36///     Ok(())
37/// }
38/// ```
39///
40/// See: <https://playwright.dev/docs/api/class-worker>
41#[derive(Clone)]
42pub struct Worker {
43    base: ChannelOwnerImpl,
44    /// The URL of this worker (from initializer)
45    url: String,
46}
47
48impl Worker {
49    /// Creates a new Worker from protocol initialization.
50    ///
51    /// Called by the object factory when the server sends a `__create__` message
52    /// for a Worker object.
53    pub fn new(
54        parent: Arc<dyn ChannelOwner>,
55        type_name: String,
56        guid: Arc<str>,
57        initializer: Value,
58    ) -> Result<Self> {
59        let url = initializer
60            .get("url")
61            .and_then(|v| v.as_str())
62            .unwrap_or("")
63            .to_string();
64
65        let base = ChannelOwnerImpl::new(
66            ParentOrConnection::Parent(parent),
67            type_name,
68            guid,
69            initializer,
70        );
71
72        Ok(Self { base, url })
73    }
74
75    /// Returns the URL of this worker.
76    ///
77    /// See: <https://playwright.dev/docs/api/class-worker#worker-url>
78    pub fn url(&self) -> &str {
79        &self.url
80    }
81
82    /// Returns the channel for sending protocol messages.
83    fn channel(&self) -> &Channel {
84        self.base.channel()
85    }
86
87    /// Evaluates a JavaScript expression in the worker context.
88    ///
89    /// The expression is evaluated in the worker's global scope. Returns the
90    /// JSON-serializable result deserialized into type `R`.
91    ///
92    /// # Arguments
93    ///
94    /// * `expression` - JavaScript expression or function body
95    /// * `arg` - Optional argument to pass to the expression
96    ///
97    /// # Errors
98    ///
99    /// Returns an error if the JavaScript expression throws.
100    ///
101    /// See: <https://playwright.dev/docs/api/class-worker#worker-evaluate>
102    pub async fn evaluate<R, T>(&self, expression: &str, arg: Option<T>) -> Result<R>
103    where
104        R: DeserializeOwned,
105        T: Serialize,
106    {
107        let serialized_arg = match arg {
108            Some(a) => serialize_argument(&a),
109            None => serialize_null(),
110        };
111
112        let params = serde_json::json!({
113            "expression": expression,
114            "arg": serialized_arg
115        });
116
117        #[derive(Deserialize)]
118        struct EvaluateResult {
119            value: Value,
120        }
121
122        let result: EvaluateResult = self.channel().send("evaluateExpression", params).await?;
123        let parsed = parse_result(&result.value);
124
125        serde_json::from_value(parsed).map_err(|e| {
126            crate::error::Error::ProtocolError(format!("Failed to deserialize result: {}", e))
127        })
128    }
129
130    /// Evaluates a JavaScript expression in the worker context, returning a JSHandle.
131    ///
132    /// Unlike [`evaluate`](Worker::evaluate) which deserializes the result,
133    /// this returns a live handle to the in-worker JavaScript object.
134    ///
135    /// # Arguments
136    ///
137    /// * `expression` - JavaScript expression or function body
138    ///
139    /// See: <https://playwright.dev/docs/api/class-worker#worker-evaluate-handle>
140    pub async fn evaluate_handle(
141        &self,
142        expression: &str,
143    ) -> Result<Arc<crate::protocol::JSHandle>> {
144        let trimmed = expression.trim();
145        let is_function = trimmed.starts_with('(')
146            || trimmed.starts_with("function")
147            || trimmed.starts_with("async ");
148
149        let params = serde_json::json!({
150            "expression": expression,
151            "isFunction": is_function,
152            "arg": {"value": {"v": "undefined"}, "handles": []}
153        });
154
155        #[derive(Deserialize)]
156        struct HandleRef {
157            guid: String,
158        }
159        #[derive(Deserialize)]
160        struct EvaluateHandleResponse {
161            handle: HandleRef,
162        }
163
164        let response: EvaluateHandleResponse = self
165            .channel()
166            .send("evaluateExpressionHandle", params)
167            .await?;
168
169        let guid = &response.handle.guid;
170        let connection = self.base.connection();
171        let mut attempts = 0;
172        let max_attempts = 20;
173
174        let handle = loop {
175            match connection
176                .get_typed::<crate::protocol::JSHandle>(guid)
177                .await
178            {
179                Ok(h) => break h,
180                Err(_) if attempts < max_attempts => {
181                    attempts += 1;
182                    tokio::time::sleep(std::time::Duration::from_millis(50)).await;
183                }
184                Err(e) => return Err(e),
185            }
186        };
187
188        Ok(Arc::new(handle))
189    }
190}
191
192impl ChannelOwner for Worker {
193    fn guid(&self) -> &str {
194        self.base.guid()
195    }
196
197    fn type_name(&self) -> &str {
198        self.base.type_name()
199    }
200
201    fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
202        self.base.parent()
203    }
204
205    fn connection(&self) -> Arc<dyn crate::server::connection::ConnectionLike> {
206        self.base.connection()
207    }
208
209    fn initializer(&self) -> &Value {
210        self.base.initializer()
211    }
212
213    fn channel(&self) -> &Channel {
214        self.base.channel()
215    }
216
217    fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
218        self.base.dispose(reason)
219    }
220
221    fn adopt(&self, child: Arc<dyn ChannelOwner>) {
222        self.base.adopt(child)
223    }
224
225    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
226        self.base.add_child(guid, child)
227    }
228
229    fn remove_child(&self, guid: &str) {
230        self.base.remove_child(guid)
231    }
232
233    fn on_event(&self, _method: &str, _params: Value) {
234        // Worker emits a "close" event when terminated; no internal state needs updating.
235    }
236
237    fn was_collected(&self) -> bool {
238        self.base.was_collected()
239    }
240
241    fn as_any(&self) -> &dyn Any {
242        self
243    }
244}
245
246impl std::fmt::Debug for Worker {
247    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248        f.debug_struct("Worker")
249            .field("guid", &self.guid())
250            .field("url", &self.url)
251            .finish()
252    }
253}