ferridriver_script/bindings/
browser_type.rs1use std::sync::Arc;
14
15use ferridriver::options::{
16 self as core_opts, BrowserTypeOptions, ChromiumTransport, ConnectOptions, ConnectOverCdpOptions, LaunchOptions,
17 LaunchPersistentContextOptions,
18};
19use ferridriver::{Browser, BrowserType};
20use rquickjs::function::Opt;
21use rquickjs::{Ctx, JsLifetime, Value, class::Class, class::Trace};
22
23use super::browser::BrowserJs;
24use super::context::BrowserContextJs;
25use crate::bindings::convert::{serde_from_js, to_rq_error};
26
27#[derive(JsLifetime, Trace)]
28#[rquickjs::class(rename = "BrowserType")]
29pub struct BrowserTypeJs {
30 #[qjs(skip_trace)]
31 inner: BrowserType,
32}
33
34impl BrowserTypeJs {
35 #[must_use]
36 pub fn new(inner: BrowserType) -> Self {
37 Self { inner }
38 }
39}
40
41#[rquickjs::methods]
42impl BrowserTypeJs {
43 #[qjs(rename = "name")]
45 pub fn name(&self) -> String {
46 self.inner.name().to_string()
47 }
48
49 #[qjs(rename = "executablePath")]
51 pub fn executable_path(&self) -> Option<String> {
52 self.inner.executable_path().map(|p| p.to_string_lossy().into_owned())
53 }
54
55 #[qjs(rename = "launch")]
57 pub async fn launch<'js>(&self, ctx: Ctx<'js>, options: Opt<Value<'js>>) -> rquickjs::Result<Value<'js>> {
58 let core = match options.0 {
59 None => LaunchOptions::default(),
60 Some(v) if v.is_undefined() || v.is_null() => LaunchOptions::default(),
61 Some(v) => parse_launch_options(&ctx, v)?,
62 };
63 let inner = self.inner.launch(core).await.map_err(|e| to_rq_error(&e))?;
64 let wrapper = BrowserJs::new(Arc::new(inner));
65 let instance = Class::instance(ctx.clone(), wrapper)?;
66 rquickjs::IntoJs::into_js(instance, &ctx)
67 }
68
69 #[qjs(rename = "connect")]
71 pub async fn connect<'js>(
72 &self,
73 ctx: Ctx<'js>,
74 ws_endpoint: String,
75 options: Opt<Value<'js>>,
76 ) -> rquickjs::Result<Value<'js>> {
77 let core = match options.0 {
78 None => ConnectOptions::default(),
79 Some(v) if v.is_undefined() || v.is_null() => ConnectOptions::default(),
80 Some(v) => parse_connect_options(&ctx, v)?,
81 };
82 let inner = self
83 .inner
84 .connect(&ws_endpoint, core)
85 .await
86 .map_err(|e| to_rq_error(&e))?;
87 let wrapper = BrowserJs::new(Arc::new(inner));
88 let instance = Class::instance(ctx.clone(), wrapper)?;
89 rquickjs::IntoJs::into_js(instance, &ctx)
90 }
91
92 #[qjs(rename = "connectOverCDP")]
94 pub async fn connect_over_cdp<'js>(
95 &self,
96 ctx: Ctx<'js>,
97 endpoint_url: String,
98 options: Opt<Value<'js>>,
99 ) -> rquickjs::Result<Value<'js>> {
100 let core = match options.0 {
101 None => ConnectOverCdpOptions::default(),
102 Some(v) if v.is_undefined() || v.is_null() => ConnectOverCdpOptions::default(),
103 Some(v) => parse_connect_over_cdp_options(&ctx, v)?,
104 };
105 let inner = self
106 .inner
107 .connect_over_cdp(&endpoint_url, core)
108 .await
109 .map_err(|e| to_rq_error(&e))?;
110 let wrapper = BrowserJs::new(Arc::new(inner));
111 let instance = Class::instance(ctx.clone(), wrapper)?;
112 rquickjs::IntoJs::into_js(instance, &ctx)
113 }
114
115 #[qjs(rename = "launchPersistentContext")]
117 pub async fn launch_persistent_context<'js>(
118 &self,
119 ctx: Ctx<'js>,
120 user_data_dir: String,
121 options: Opt<Value<'js>>,
122 ) -> rquickjs::Result<Value<'js>> {
123 let (launch, context) = match options.0 {
124 None => (LaunchOptions::default(), core_opts::BrowserContextOptions::default()),
125 Some(v) if v.is_undefined() || v.is_null() => {
126 (LaunchOptions::default(), core_opts::BrowserContextOptions::default())
127 },
128 Some(v) => {
129 let launch = parse_launch_options(&ctx, v.clone())?;
130 let context = parse_context_options(&ctx, v)?;
131 (launch, context)
132 },
133 };
134 let core = LaunchPersistentContextOptions { launch, context };
135 let ctx_ref = self
136 .inner
137 .launch_persistent_context(std::path::Path::new(&user_data_dir), core)
138 .await
139 .map_err(|e| to_rq_error(&e))?;
140 let wrapper = BrowserContextJs::new(Arc::new(ctx_ref));
141 let instance = Class::instance(ctx.clone(), wrapper)?;
142 rquickjs::IntoJs::into_js(instance, &ctx)
143 }
144}
145
146#[derive(serde::Deserialize, Default)]
147#[serde(rename_all = "camelCase", default)]
148struct JsLaunchOptions {
149 headless: Option<bool>,
150 executable_path: Option<String>,
151 args: Option<Vec<String>>,
152 channel: Option<String>,
153 slow_mo: Option<u64>,
154 timeout: Option<u64>,
155 downloads_path: Option<String>,
156 traces_dir: Option<String>,
157}
158
159#[derive(serde::Deserialize, Default)]
160#[serde(rename_all = "camelCase", default)]
161struct JsConnectOptions {
162 headers: Option<rustc_hash::FxHashMap<String, String>>,
163 slow_mo: Option<u64>,
164 timeout: Option<u64>,
165 expose_network: Option<String>,
166}
167
168#[derive(serde::Deserialize, Default)]
169#[serde(rename_all = "camelCase", default)]
170struct JsConnectOverCdpOptions {
171 headers: Option<rustc_hash::FxHashMap<String, String>>,
172 slow_mo: Option<u64>,
173 timeout: Option<u64>,
174}
175
176fn parse_launch_options<'js>(ctx: &Ctx<'js>, value: Value<'js>) -> rquickjs::Result<LaunchOptions> {
177 let parsed: JsLaunchOptions = serde_from_js(ctx, value)?;
178 Ok(LaunchOptions {
179 headless: parsed.headless,
180 executable_path: parsed.executable_path,
181 args: parsed.args.unwrap_or_default(),
182 channel: parsed.channel,
183 env: None,
184 slow_mo: parsed.slow_mo,
185 timeout: parsed.timeout,
186 downloads_path: parsed.downloads_path.map(std::path::PathBuf::from),
187 ignore_default_args: None,
188 handle_sighup: None,
189 handle_sigint: None,
190 handle_sigterm: None,
191 chromium_sandbox: None,
192 firefox_user_prefs: None,
193 proxy: None,
194 traces_dir: parsed.traces_dir.map(std::path::PathBuf::from),
195 })
196}
197
198fn parse_connect_options<'js>(ctx: &Ctx<'js>, value: Value<'js>) -> rquickjs::Result<ConnectOptions> {
199 let parsed: JsConnectOptions = serde_from_js(ctx, value)?;
200 Ok(ConnectOptions {
201 headers: parsed.headers,
202 slow_mo: parsed.slow_mo,
203 timeout: parsed.timeout,
204 expose_network: parsed.expose_network,
205 })
206}
207
208fn parse_connect_over_cdp_options<'js>(ctx: &Ctx<'js>, value: Value<'js>) -> rquickjs::Result<ConnectOverCdpOptions> {
209 let parsed: JsConnectOverCdpOptions = serde_from_js(ctx, value)?;
210 Ok(ConnectOverCdpOptions {
211 headers: parsed.headers,
212 slow_mo: parsed.slow_mo,
213 timeout: parsed.timeout,
214 })
215}
216
217fn parse_context_options<'js>(ctx: &Ctx<'js>, value: Value<'js>) -> rquickjs::Result<core_opts::BrowserContextOptions> {
218 let parsed: super::browser::JsBrowserContextOptions = serde_from_js(ctx, value)?;
222 Ok(parsed.into_core())
223}
224
225fn chromium_factory<'js>(ctx: Ctx<'js>, opts: Opt<Value<'js>>) -> rquickjs::Result<Class<'js, BrowserTypeJs>> {
226 let transport = match opts.0 {
227 None => None,
228 Some(v) if v.is_undefined() || v.is_null() => None,
229 Some(v) => {
230 #[derive(serde::Deserialize, Default)]
231 struct ChromiumOpts {
232 transport: Option<String>,
233 }
234 let parsed: ChromiumOpts = serde_from_js(&ctx, v)?;
235 parsed.transport.and_then(|t| match t.as_str() {
236 "ws" => Some(ChromiumTransport::Ws),
237 "pipe" => Some(ChromiumTransport::Pipe),
238 _ => None,
239 })
240 },
241 };
242 let bt = BrowserType::chromium_with(&BrowserTypeOptions { transport });
243 Class::instance(ctx, BrowserTypeJs::new(bt))
244}
245
246fn firefox_factory(ctx: Ctx<'_>) -> rquickjs::Result<Class<'_, BrowserTypeJs>> {
247 Class::instance(ctx, BrowserTypeJs::new(BrowserType::firefox()))
248}
249
250fn webkit_factory(ctx: Ctx<'_>) -> rquickjs::Result<Class<'_, BrowserTypeJs>> {
251 Class::instance(ctx, BrowserTypeJs::new(BrowserType::webkit()))
252}
253
254pub fn install_browser_type(ctx: &Ctx<'_>) -> rquickjs::Result<()> {
261 Class::<BrowserTypeJs>::define(&ctx.globals())?;
262
263 ctx
264 .globals()
265 .set("chromium", rquickjs::Function::new(ctx.clone(), chromium_factory)?)?;
266 ctx
267 .globals()
268 .set("firefox", rquickjs::Function::new(ctx.clone(), firefox_factory)?)?;
269 ctx
270 .globals()
271 .set("webkit", rquickjs::Function::new(ctx.clone(), webkit_factory)?)?;
272
273 let _ = std::marker::PhantomData::<Browser>;
276
277 Ok(())
278}