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}