Skip to main content

playwright_rs/protocol/browser_context/
bindings.rs

1use super::{BindingCallback, BindingCallbackFuture, BrowserContext};
2use crate::error::Result;
3use crate::server::channel_owner::ChannelOwner;
4use serde_json::Value;
5use std::future::Future;
6use std::sync::Arc;
7
8/// Init scripts, exposed functions and bindings.
9impl BrowserContext {
10    /// Adds a script which would be evaluated in one of the following scenarios:
11    ///
12    /// - Whenever a page is created in the browser context or is navigated.
13    /// - Whenever a child frame is attached or navigated in any page in the browser context.
14    ///
15    /// The script is evaluated after the document was created but before any of its scripts
16    /// were run. This is useful to amend the JavaScript environment, e.g. to seed Math.random.
17    ///
18    /// # Arguments
19    ///
20    /// * `script` - Script to be evaluated in all pages in the browser context.
21    ///
22    /// # Errors
23    ///
24    /// Returns error if:
25    /// - Context has been closed
26    /// - Communication with browser process fails
27    ///
28    /// See: <https://playwright.dev/docs/api/class-browsercontext#browser-context-add-init-script>
29    #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
30    pub async fn add_init_script(&self, script: &str) -> Result<()> {
31        self.channel()
32            .send_no_result("addInitScript", serde_json::json!({ "source": script }))
33            .await
34    }
35
36    /// Exposes a Rust function to every page in this browser context as
37    /// `window[name]` in JavaScript.
38    ///
39    /// When JavaScript code calls `window[name](arg1, arg2, …)` the Playwright
40    /// server fires a `bindingCall` event that invokes `callback` with the
41    /// deserialized arguments. The return value of `callback` is serialized back
42    /// to JavaScript so the `await window[name](…)` expression resolves with it.
43    ///
44    /// The binding is injected into every existing page and every new page
45    /// created in this context.
46    ///
47    /// # Arguments
48    ///
49    /// * `name`     – JavaScript identifier that will be available as `window[name]`.
50    /// * `callback` – Async closure called with `Vec<serde_json::Value>` (the JS
51    ///   arguments) and returning `serde_json::Value` (the result).
52    ///
53    /// # Errors
54    ///
55    /// Returns error if:
56    /// - The context has been closed.
57    /// - Communication with the browser process fails.
58    ///
59    /// See: <https://playwright.dev/docs/api/class-browsercontext#browser-context-expose-function>
60    #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid(), name = %name))]
61    pub async fn expose_function<F, Fut>(&self, name: &str, callback: F) -> Result<()>
62    where
63        F: Fn(Vec<serde_json::Value>) -> Fut + Send + Sync + 'static,
64        Fut: Future<Output = serde_json::Value> + Send + 'static,
65    {
66        self.expose_binding_internal(name, callback).await
67    }
68
69    /// Exposes a Rust function to every page in this browser context as
70    /// `window[name]` in JavaScript.
71    ///
72    /// Currently identical to [`expose_function`](Self::expose_function):
73    /// arguments arrive as plain serialized values. Upstream Playwright's
74    /// `exposeBinding` can additionally hand the callback a source
75    /// (page/frame) descriptor, which this crate does not surface yet.
76    ///
77    /// # Arguments
78    ///
79    /// * `name`     – JavaScript identifier.
80    /// * `callback` – Async closure with `Vec<serde_json::Value>` → `serde_json::Value`.
81    ///
82    /// # Errors
83    ///
84    /// Returns error if:
85    /// - The context has been closed.
86    /// - Communication with the browser process fails.
87    ///
88    /// See: <https://playwright.dev/docs/api/class-browsercontext#browser-context-expose-binding>
89    #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid(), name = %name))]
90    pub async fn expose_binding<F, Fut>(&self, name: &str, callback: F) -> Result<()>
91    where
92        F: Fn(Vec<serde_json::Value>) -> Fut + Send + Sync + 'static,
93        Fut: Future<Output = serde_json::Value> + Send + 'static,
94    {
95        self.expose_binding_internal(name, callback).await
96    }
97
98    /// Internal implementation shared by expose_function and expose_binding.
99    async fn expose_binding_internal<F, Fut>(&self, name: &str, callback: F) -> Result<()>
100    where
101        F: Fn(Vec<serde_json::Value>) -> Fut + Send + Sync + 'static,
102        Fut: Future<Output = serde_json::Value> + Send + 'static,
103    {
104        // Wrap callback with type erasure
105        let callback: BindingCallback = Arc::new(move |args: Vec<serde_json::Value>| {
106            Box::pin(callback(args)) as BindingCallbackFuture
107        });
108
109        // Store the callback before sending the RPC so that a race-condition
110        // where a bindingCall arrives before we finish registering is avoided.
111        self.binding_callbacks
112            .lock()
113            .unwrap()
114            .insert(name.to_string(), callback);
115
116        // Tell the Playwright server to inject window[name] into every page.
117        //
118        // The protocol also accepts `noGlobal`, which suppresses that
119        // injection. It is deliberately not exposed: it exists to support
120        // passing functions as evaluate arguments, where the binding is
121        // called through the bindings controller rather than off `window`.
122        self.channel()
123            .send_no_result("exposeBinding", serde_json::json!({ "name": name }))
124            .await
125    }
126
127    /// Deserializes binding call arguments from Playwright's protocol format.
128    ///
129    /// The `args` field in the BindingCall initializer is a JSON array of
130    /// type-tagged values; each is parsed into plain JSON. `Page`'s
131    /// `bindingCall` dispatch shares it.
132    pub(crate) fn deserialize_binding_args(raw_args: &Value) -> Vec<Value> {
133        let Some(arr) = raw_args.as_array() else {
134            return vec![];
135        };
136
137        arr.iter()
138            .map(|arg| {
139                // Each arg is a direct Playwright type-tagged value, e.g. {"n": 3} or {"s": "hello"}
140                // (NOT wrapped in {"value": ..., "handles": []} — that format is only for evaluate args)
141                crate::protocol::evaluate_conversion::parse_value(arg, None)
142            })
143            .collect()
144    }
145}