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    #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
103    pub async fn evaluate<R, T>(&self, expression: &str, arg: Option<T>) -> Result<R>
104    where
105        R: DeserializeOwned,
106        T: Serialize,
107    {
108        let serialized_arg = match arg {
109            Some(a) => serialize_argument(&a),
110            None => serialize_null(),
111        };
112
113        let params = serde_json::json!({
114            "expression": expression,
115            "arg": serialized_arg
116        });
117
118        #[derive(Deserialize)]
119        struct EvaluateResult {
120            value: Value,
121        }
122
123        let result: EvaluateResult = self.channel().send("evaluateExpression", params).await?;
124        let parsed = parse_result(&result.value);
125
126        serde_json::from_value(parsed).map_err(|e| {
127            crate::error::Error::ProtocolError(format!("Failed to deserialize result: {}", e))
128        })
129    }
130
131    /// Evaluates a JavaScript expression in the worker context, returning a JSHandle.
132    ///
133    /// Unlike [`evaluate`](Worker::evaluate) which deserializes the result,
134    /// this returns a live handle to the in-worker JavaScript object.
135    ///
136    /// # Arguments
137    ///
138    /// * `expression` - JavaScript expression or function body
139    ///
140    /// See: <https://playwright.dev/docs/api/class-worker#worker-evaluate-handle>
141    #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
142    pub async fn evaluate_handle(
143        &self,
144        expression: &str,
145    ) -> Result<Arc<crate::protocol::JSHandle>> {
146        let trimmed = expression.trim();
147        let is_function = trimmed.starts_with('(')
148            || trimmed.starts_with("function")
149            || trimmed.starts_with("async ");
150
151        let params = serde_json::json!({
152            "expression": expression,
153            "isFunction": is_function,
154            "arg": {"value": {"v": "undefined"}, "handles": []}
155        });
156
157        #[derive(Deserialize)]
158        struct HandleRef {
159            guid: String,
160        }
161        #[derive(Deserialize)]
162        struct EvaluateHandleResponse {
163            handle: HandleRef,
164        }
165
166        let response: EvaluateHandleResponse = self
167            .channel()
168            .send("evaluateExpressionHandle", params)
169            .await?;
170
171        let guid = &response.handle.guid;
172        let connection = self.base.connection();
173        let mut attempts = 0;
174        let max_attempts = 20;
175
176        let handle = loop {
177            match connection
178                .get_typed::<crate::protocol::JSHandle>(guid)
179                .await
180            {
181                Ok(h) => break h,
182                Err(_) if attempts < max_attempts => {
183                    attempts += 1;
184                    tokio::time::sleep(std::time::Duration::from_millis(50)).await;
185                }
186                Err(e) => return Err(e),
187            }
188        };
189
190        Ok(Arc::new(handle))
191    }
192}
193
194impl ChannelOwner for Worker {
195    fn guid(&self) -> &str {
196        self.base.guid()
197    }
198
199    fn type_name(&self) -> &str {
200        self.base.type_name()
201    }
202
203    fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
204        self.base.parent()
205    }
206
207    fn connection(&self) -> Arc<dyn crate::server::connection::ConnectionLike> {
208        self.base.connection()
209    }
210
211    fn initializer(&self) -> &Value {
212        self.base.initializer()
213    }
214
215    fn channel(&self) -> &Channel {
216        self.base.channel()
217    }
218
219    fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
220        self.base.dispose(reason)
221    }
222
223    fn adopt(&self, child: Arc<dyn ChannelOwner>) {
224        self.base.adopt(child)
225    }
226
227    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
228        self.base.add_child(guid, child)
229    }
230
231    fn remove_child(&self, guid: &str) {
232        self.base.remove_child(guid)
233    }
234
235    fn on_event(&self, _method: &str, _params: Value) {
236        // Worker emits a "close" event when terminated; no internal state needs updating.
237    }
238
239    fn was_collected(&self) -> bool {
240        self.base.was_collected()
241    }
242
243    fn as_any(&self) -> &dyn Any {
244        self
245    }
246}
247
248impl std::fmt::Debug for Worker {
249    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250        f.debug_struct("Worker")
251            .field("guid", &self.guid())
252            .field("url", &self.url)
253            .finish()
254    }
255}