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 serde::de::DeserializeOwned;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::any::Any;
11use std::sync::Arc;
12
13/// Worker represents a Web Worker or Service Worker.
14///
15/// Workers are created by the page using the `Worker` constructor or by browsers
16/// for registered service workers. They run JS in an isolated global scope.
17///
18/// # Example
19///
20/// ```no_run
21/// use playwright_rs::protocol::Playwright;
22///
23/// #[tokio::main]
24/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
25///     let playwright = Playwright::launch().await?;
26///     let browser = playwright.chromium().launch().await?;
27///     let page = browser.new_page().await?;
28///
29///     page.on_worker(|worker| {
30///         println!("Worker created: {}", worker.url());
31///         Box::pin(async move { Ok(()) })
32///     }).await?;
33///
34///     browser.close().await?;
35///     Ok(())
36/// }
37/// ```
38///
39/// See: <https://playwright.dev/docs/api/class-worker>
40#[derive(Clone)]
41pub struct Worker {
42    base: ChannelOwnerImpl,
43    /// The URL of this worker (from initializer)
44    url: String,
45}
46
47impl Worker {
48    /// Creates a new Worker from protocol initialization.
49    ///
50    /// Called by the object factory when the server sends a `__create__` message
51    /// for a Worker object.
52    pub fn new(
53        parent: Arc<dyn ChannelOwner>,
54        type_name: String,
55        guid: Arc<str>,
56        initializer: Value,
57    ) -> Result<Self> {
58        let url = initializer
59            .get("url")
60            .and_then(|v| v.as_str())
61            .unwrap_or("")
62            .to_string();
63
64        let base = ChannelOwnerImpl::new(
65            ParentOrConnection::Parent(parent),
66            type_name,
67            guid,
68            initializer,
69        );
70
71        Ok(Self { base, url })
72    }
73
74    /// Returns the URL of this worker.
75    ///
76    /// See: <https://playwright.dev/docs/api/class-worker#worker-url>
77    pub fn url(&self) -> &str {
78        &self.url
79    }
80
81    /// Returns the channel for sending protocol messages.
82    fn channel(&self) -> &Channel {
83        self.base.channel()
84    }
85
86    /// Evaluates a JavaScript expression in the worker context.
87    ///
88    /// The expression is evaluated in the worker's global scope. Returns the
89    /// JSON-serializable result deserialized into type `R`.
90    ///
91    /// # Arguments
92    ///
93    /// * `expression` - JavaScript expression or function body
94    /// * `arg` - Optional argument to pass to the expression
95    ///
96    /// # Errors
97    ///
98    /// Returns an error if the JavaScript expression throws.
99    ///
100    /// See: <https://playwright.dev/docs/api/class-worker#worker-evaluate>
101    #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
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    #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
141    pub async fn evaluate_handle(
142        &self,
143        expression: &str,
144    ) -> Result<Arc<crate::protocol::JSHandle>> {
145        // No isFunction: absent, the driver auto-detects a function
146        // expression; a client-side guess misreads bare arrows.
147        let params = serde_json::json!({
148            "expression": expression,
149            "arg": {"value": {"v": "undefined"}, "handles": []}
150        });
151
152        #[derive(Deserialize)]
153        struct HandleRef {
154            guid: String,
155        }
156        #[derive(Deserialize)]
157        struct EvaluateHandleResponse {
158            handle: HandleRef,
159        }
160
161        let response: EvaluateHandleResponse = self
162            .channel()
163            .send("evaluateExpressionHandle", params)
164            .await?;
165
166        let guid = &response.handle.guid;
167        let handle = crate::protocol::JSHandle::wait_for(&self.base.connection(), guid).await?;
168
169        Ok(Arc::new(handle))
170    }
171}
172
173impl ChannelOwner for Worker {
174    fn guid(&self) -> &str {
175        self.base.guid()
176    }
177
178    fn type_name(&self) -> &str {
179        self.base.type_name()
180    }
181
182    fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
183        self.base.parent()
184    }
185
186    fn connection(&self) -> Arc<dyn crate::server::connection::ConnectionLike> {
187        self.base.connection()
188    }
189
190    fn initializer(&self) -> &Value {
191        self.base.initializer()
192    }
193
194    fn channel(&self) -> &Channel {
195        self.base.channel()
196    }
197
198    fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
199        self.base.dispose(reason)
200    }
201
202    fn adopt(&self, child: Arc<dyn ChannelOwner>) {
203        self.base.adopt(child)
204    }
205
206    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
207        self.base.add_child(guid, child)
208    }
209
210    fn remove_child(&self, guid: &str) {
211        self.base.remove_child(guid)
212    }
213
214    fn on_event(&self, _method: &str, _params: Value) {
215        // Worker emits a "close" event when terminated; no internal state needs updating.
216    }
217
218    fn was_collected(&self) -> bool {
219        self.base.was_collected()
220    }
221
222    fn as_any(&self) -> &dyn Any {
223        self
224    }
225}
226
227impl std::fmt::Debug for Worker {
228    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229        f.debug_struct("Worker")
230            .field("guid", &self.guid())
231            .field("url", &self.url)
232            .finish()
233    }
234}