Skip to main content

playwright_rs/protocol/
binding_call.rs

1// Copyright 2026 Paul Adamson
2// Licensed under the Apache License, Version 2.0
3//
4// BindingCall protocol object
5//
6// Represents a single invocation of a binding registered via expose_function
7// or expose_binding. When JavaScript code calls the exposed function, the
8// Playwright server creates a BindingCall object and fires a "bindingCall"
9// event on the BrowserContext.
10//
11// The BindingCall must be resolved (via "fulfill") or rejected (via "reject")
12// to unblock the JS caller.
13//
14// See: https://playwright.dev/docs/api/class-browsercontext#browser-context-expose-function
15
16use crate::error::Result;
17use crate::server::channel_owner::{ChannelOwner, ChannelOwnerImpl, ParentOrConnection};
18use serde_json::Value;
19use std::any::Any;
20use std::sync::Arc;
21
22/// BindingCall represents a single JS → Rust callback invocation.
23///
24/// When JavaScript calls an exposed function (registered via `expose_function`
25/// or `expose_binding`), the server sends a `bindingCall` event on the
26/// BrowserContext channel containing the GUID of a freshly created BindingCall
27/// object. The Rust handler must call either [`resolve`](BindingCall::resolve)
28/// or [`reject`](BindingCall::reject) to unblock the JS caller.
29///
30/// See: <https://playwright.dev/docs/api/class-browsercontext#browser-context-expose-function>
31#[derive(Clone)]
32pub struct BindingCall {
33    base: ChannelOwnerImpl,
34}
35
36impl BindingCall {
37    /// Creates a new BindingCall from protocol initialization.
38    ///
39    /// Called by the object factory when the server sends a `__create__`
40    /// message for a BindingCall object.
41    pub fn new(
42        parent: Arc<dyn ChannelOwner>,
43        type_name: String,
44        guid: Arc<str>,
45        initializer: Value,
46    ) -> Result<Self> {
47        let base = ChannelOwnerImpl::new(
48            ParentOrConnection::Parent(parent),
49            type_name,
50            guid,
51            initializer,
52        );
53        Ok(Self { base })
54    }
55
56    /// Returns the name of the binding that was called.
57    ///
58    /// Matches the `name` argument passed to `expose_function` / `expose_binding`.
59    pub fn name(&self) -> &str {
60        self.base
61            .initializer()
62            .get("name")
63            .and_then(|v| v.as_str())
64            .unwrap_or("")
65    }
66
67    /// Returns the raw serialized arguments sent by the JS caller.
68    ///
69    /// This is the `args` array from the initializer, in Playwright's
70    /// type-tagged protocol format (e.g. `[{"n": 3}, {"n": 7}]`).
71    pub fn args(&self) -> &Value {
72        self.base.initializer().get("args").unwrap_or(&Value::Null)
73    }
74
75    /// Resolves the binding call with a result value.
76    ///
77    /// Sends the `resolve` RPC back to the Playwright server so the
78    /// JS `await` of the exposed function resolves with `result`.
79    ///
80    /// # Arguments
81    ///
82    /// * `result` - The value to return to the JavaScript caller, already
83    ///   serialized in Playwright's `serialize_argument` format
84    ///   (`{"value": ..., "handles": []}`).
85    ///
86    /// # Errors
87    ///
88    /// Returns error if communication with the browser process fails.
89    pub async fn resolve(&self, result: Value) -> Result<()> {
90        self.base
91            .channel()
92            .send_no_result("resolve", serde_json::json!({ "result": result }))
93            .await
94    }
95
96    /// Rejects the binding call with an error.
97    ///
98    /// Sends the `reject` RPC so the JS `await` of the exposed function
99    /// rejects with the given error message.
100    ///
101    /// # Arguments
102    ///
103    /// * `message` - Human-readable error description sent to the JS caller.
104    ///
105    /// # Errors
106    ///
107    /// Returns error if communication with the browser process fails.
108    pub async fn reject(&self, message: &str) -> Result<()> {
109        self.base
110            .channel()
111            .send_no_result(
112                "reject",
113                serde_json::json!({ "error": { "error": { "message": message, "name": "Error" } } }),
114            )
115            .await
116    }
117}
118
119impl ChannelOwner for BindingCall {
120    fn guid(&self) -> &str {
121        self.base.guid()
122    }
123
124    fn type_name(&self) -> &str {
125        self.base.type_name()
126    }
127
128    fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
129        self.base.parent()
130    }
131
132    fn connection(&self) -> Arc<dyn crate::server::connection::ConnectionLike> {
133        self.base.connection()
134    }
135
136    fn initializer(&self) -> &Value {
137        self.base.initializer()
138    }
139
140    fn channel(&self) -> &crate::server::channel::Channel {
141        self.base.channel()
142    }
143
144    fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
145        self.base.dispose(reason)
146    }
147
148    fn adopt(&self, child: Arc<dyn ChannelOwner>) {
149        self.base.adopt(child)
150    }
151
152    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
153        self.base.add_child(guid, child)
154    }
155
156    fn remove_child(&self, guid: &str) {
157        self.base.remove_child(guid)
158    }
159
160    fn on_event(&self, _method: &str, _params: Value) {
161        // BindingCall does not emit events
162    }
163
164    fn was_collected(&self) -> bool {
165        self.base.was_collected()
166    }
167
168    fn as_any(&self) -> &dyn Any {
169        self
170    }
171}
172
173impl std::fmt::Debug for BindingCall {
174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175        f.debug_struct("BindingCall")
176            .field("guid", &self.guid())
177            .field("name", &self.name())
178            .finish()
179    }
180}