1pub mod accessibility_yaml;
2use serde::{Deserialize, Serialize};
3
4pub const ALLWRIGHT_PLUGIN_API_VERSION: u32 = 1;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum SurfaceFamily {
8 Web,
9 Mobile,
10 Desktop,
11}
12
13#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
14#[serde(rename_all = "snake_case")]
15pub enum BrowserKind {
16 Chromium,
17 Firefox,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct SurfacePluginDescriptor {
22 pub id: &'static str,
23 pub family: SurfaceFamily,
24 pub version: &'static str,
25 pub description: &'static str,
26}
27
28pub trait SurfacePlugin: Send + Sync {
29 fn descriptor(&self) -> SurfacePluginDescriptor;
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
33pub struct ChromeLaunchInfo {
34 pub browser: String,
35 pub note: String,
36 pub cdp_websocket_url: String,
37 pub user_data_dir: String,
38 pub process_id: u32,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
42pub struct BrowserLaunchInfo {
43 pub browser_kind: BrowserKind,
44 pub browser: String,
45 pub note: String,
46 pub user_data_dir: String,
47 pub process_id: u32,
48 pub browser_session: BrowserSessionHandle,
49 pub initial_page: PageInfo,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
53pub struct ChromeTabInfo {
54 pub note: String,
55 pub target_id: String,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
59pub struct TabNavigationInfo {
60 pub url: String,
61 pub note: String,
62 pub page_session: PageSessionHandle,
63 pub automation: AutomationSessionInfo,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
67pub struct ChromiumBidiMapperInfo {
68 pub package_version: String,
69 pub mapper_target_id: String,
70 pub mapper_session_id: String,
71 pub note: String,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
75#[serde(tag = "kind", rename_all = "snake_case")]
76pub enum BrowserSessionHandle {
77 Chromium {
78 cdp_websocket_url: String,
79 },
80 Firefox {
81 connection_id: String,
82 bidi_session_id: String,
83 },
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
87#[serde(tag = "kind", rename_all = "snake_case")]
88pub enum PageSessionHandle {
89 Chromium {
90 target_id: String,
91 browsing_context_id: Option<String>,
92 #[serde(default)]
93 mapper_target_id: Option<String>,
94 },
95 Firefox {
96 browsing_context_id: String,
97 },
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
101pub struct PageInfo {
102 pub note: String,
103 pub page_session: PageSessionHandle,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
107pub enum HookType {
108 Dialog,
109 NewPage,
110 FileChooser,
111 Download,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
115pub struct HookRegistration {
116 pub opaque_state: String,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
120#[serde(tag = "hook_type", content = "value", rename_all = "snake_case")]
121pub enum HookResult {
122 Dialog(DialogInfo),
123 NewPage(PageInfo),
124 FileChooser(FileChooserInfo),
125 Download(DownloadInfo),
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
129pub struct DialogInfo {
130 pub dialog_id: String,
131 pub kind: String,
132 pub message: String,
133 pub default_value: String,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
137pub struct FileChooserInfo {
138 pub file_chooser_id: String,
139 pub is_multiple: bool,
140 pub note: String,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
144pub struct FileChooserFilesSetInfo {
145 pub file_chooser_id: String,
146 pub files: Vec<String>,
147 pub note: String,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
151pub struct DownloadInfo {
152 pub download_id: String,
153 pub url: String,
154 pub suggested_filename: String,
155 pub note: String,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
159pub struct DownloadSavedInfo {
160 pub download_id: String,
161 pub path: String,
162 pub suggested_filename: String,
163 pub size: u64,
164 pub note: String,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
168pub struct AutomationSessionInfo {
169 pub bidi_session_id: String,
170 pub note: String,
171 pub mapper_target_id: Option<String>,
172 pub mapper_session_id: Option<String>,
173 pub package_version: Option<String>,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
177pub struct ClickInfo {
178 pub css_selector: String,
179 pub note: String,
180 pub bidi_session_id: String,
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
184pub struct ElementCountInfo {
185 pub css_selector: String,
186 pub count: u32,
187 pub note: String,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
191pub struct HighlightElementsInfo {
192 pub css_selector: String,
193 pub count: u32,
194 pub note: String,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
198pub struct FocusInfo {
199 pub css_selector: String,
200 pub note: String,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
204pub struct FillInfo {
205 pub css_selector: String,
206 pub value: String,
207 pub note: String,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
211pub struct HoverInfo {
212 pub css_selector: String,
213 pub note: String,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
217pub struct PressKeyInfo {
218 pub css_selector: String,
219 pub key: String,
220 pub note: String,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
224pub struct TextInfo {
225 pub css_selector: String,
226 pub text: String,
227 pub note: String,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
231pub struct WaitForSelectorInfo {
232 pub css_selector: String,
233 pub visible: bool,
234 pub note: String,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
238pub struct AccessibilitySnapshotInfo {
239 pub snapshot: String,
240 pub format: String,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
244pub struct ScreenshotInfo {
245 pub png_data: Vec<u8>,
246 pub note: String,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
250#[serde(tag = "command", rename_all = "snake_case")]
251pub enum PluginCommand {
252 LaunchBrowser {
253 browser_kind: BrowserKind,
254 browser_binary: Option<String>,
255 },
256 ResolveFrame {
257 browser_session: BrowserSessionHandle,
258 page_session: PageSessionHandle,
259 css_selector: String,
260 timeout_ms: u64,
261 },
262 OpenPage {
263 browser_session: BrowserSessionHandle,
264 },
265 RegisterHook {
266 browser_session: BrowserSessionHandle,
267 page_session: PageSessionHandle,
268 hook_type: HookType,
269 },
270 PollHook {
271 browser_session: BrowserSessionHandle,
272 registration: HookRegistration,
273 },
274 HandleDialog {
275 browser_session: BrowserSessionHandle,
276 page_session: PageSessionHandle,
277 dialog_id: String,
278 accept: bool,
279 prompt_text: Option<String>,
280 },
281 SetFileChooserFiles {
282 browser_session: BrowserSessionHandle,
283 page_session: PageSessionHandle,
284 file_chooser_id: String,
285 files: Vec<String>,
286 },
287 SaveDownload {
288 browser_session: BrowserSessionHandle,
289 page_session: PageSessionHandle,
290 download_id: String,
291 path: String,
292 },
293 ClosePage {
294 browser_session: BrowserSessionHandle,
295 page_session: PageSessionHandle,
296 },
297 NavigatePage {
298 browser_session: BrowserSessionHandle,
299 page_session: PageSessionHandle,
300 url: String,
301 },
302 ClickElement {
303 browser_session: BrowserSessionHandle,
304 page_session: PageSessionHandle,
305 css_selector: String,
306 },
307 CountElements {
308 browser_session: BrowserSessionHandle,
309 page_session: PageSessionHandle,
310 css_selector: String,
311 },
312 HighlightElements {
313 browser_session: BrowserSessionHandle,
314 page_session: PageSessionHandle,
315 css_selector: String,
316 duration_ms: u64,
317 },
318 FocusElement {
319 browser_session: BrowserSessionHandle,
320 page_session: PageSessionHandle,
321 css_selector: String,
322 },
323 FillElement {
324 browser_session: BrowserSessionHandle,
325 page_session: PageSessionHandle,
326 css_selector: String,
327 value: String,
328 },
329 HoverElement {
330 browser_session: BrowserSessionHandle,
331 page_session: PageSessionHandle,
332 css_selector: String,
333 },
334 PressKey {
335 browser_session: BrowserSessionHandle,
336 page_session: PageSessionHandle,
337 css_selector: String,
338 key: String,
339 text: Option<String>,
340 },
341 Capture {
342 browser_session: BrowserSessionHandle,
343 page_session: PageSessionHandle,
344 kind: String,
345 css_selector: String,
346 attribute_name: String,
347 },
348 GetTextContent {
349 browser_session: BrowserSessionHandle,
350 page_session: PageSessionHandle,
351 css_selector: String,
352 },
353 GetInnerText {
354 browser_session: BrowserSessionHandle,
355 page_session: PageSessionHandle,
356 css_selector: String,
357 },
358 WaitForSelector {
359 browser_session: BrowserSessionHandle,
360 page_session: PageSessionHandle,
361 css_selector: String,
362 visible: bool,
363 },
364 AccessibilitySnapshot {
365 browser_session: BrowserSessionHandle,
366 page_session: PageSessionHandle,
367 format: String,
368 #[serde(default)]
369 mode: String,
370 },
371 Screenshot {
372 browser_session: BrowserSessionHandle,
373 page_session: PageSessionHandle,
374 full_page: bool,
375 },
376 OpenChromeWindow {
377 chrome_binary: Option<String>,
378 },
379 DiscoverInitialTab {
380 cdp_websocket_url: String,
381 },
382 OpenChromeTab {
383 cdp_websocket_url: String,
384 },
385 CloseBrowserProcess {
386 process_id: u32,
387 },
388 CloseChromeTab {
389 cdp_websocket_url: String,
390 target_id: String,
391 },
392 NavigateChromeTab {
393 cdp_websocket_url: String,
394 target_id: String,
395 url: String,
396 },
397 InjectChromiumBidiMapper {
398 cdp_websocket_url: String,
399 },
400 ResolveBidiContextForTab {
401 cdp_websocket_url: String,
402 mapper_target_id: Option<String>,
403 browsing_context_id: Option<String>,
404 url: Option<String>,
405 },
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
409#[serde(tag = "result", rename_all = "snake_case")]
410pub enum PluginResult {
411 LaunchBrowser(BrowserLaunchInfo),
412 ResolveFrame(PageInfo),
413 OpenPage(PageInfo),
414 RegisterHook(HookRegistration),
415 PollHook(HookResult),
416 HandleDialog,
417 SetFileChooserFiles(FileChooserFilesSetInfo),
418 SaveDownload(DownloadSavedInfo),
419 ClosePage,
420 NavigatePage(TabNavigationInfo),
421 ClickElement(ClickInfo),
422 CountElements(ElementCountInfo),
423 HighlightElements(HighlightElementsInfo),
424 FocusElement(FocusInfo),
425 FillElement(FillInfo),
426 HoverElement(HoverInfo),
427 PressKey(PressKeyInfo),
428 Capture(CaptureInfo),
429 GetTextContent(TextInfo),
430 GetInnerText(TextInfo),
431 WaitForSelector(WaitForSelectorInfo),
432 AccessibilitySnapshot(AccessibilitySnapshotInfo),
433 Screenshot(ScreenshotInfo),
434 OpenChromeWindow(ChromeLaunchInfo),
435 DiscoverInitialTab(ChromeTabInfo),
436 OpenChromeTab(ChromeTabInfo),
437 CloseBrowserProcess,
438 CloseChromeTab,
439 NavigateChromeTab(TabNavigationInfo),
440 InjectChromiumBidiMapper(ChromiumBidiMapperInfo),
441 ResolveBidiContextForTab {
442 browsing_context_id: String,
443 mapper: ChromiumBidiMapperInfo,
444 },
445}
446
447#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
448pub struct PluginEnvelope {
449 pub ok: bool,
450 pub result: Option<PluginResult>,
451 pub error: Option<String>,
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457
458 #[test]
459 fn poll_hook_envelope_round_trips_without_duplicate_result_fields() {
460 let envelope = PluginEnvelope {
461 ok: true,
462 result: Some(PluginResult::PollHook(HookResult::NewPage(PageInfo {
463 note: "opened".to_string(),
464 page_session: PageSessionHandle::Chromium {
465 target_id: "target-2".to_string(),
466 browsing_context_id: None,
467 mapper_target_id: None,
468 },
469 }))),
470 error: None,
471 };
472
473 let json = serde_json::to_string(&envelope).unwrap();
474 assert_eq!(
475 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
476 envelope
477 );
478 }
479
480 #[test]
481 fn dialog_hook_envelope_round_trips() {
482 let envelope = PluginEnvelope {
483 ok: true,
484 result: Some(PluginResult::PollHook(HookResult::Dialog(DialogInfo {
485 dialog_id: "dialog-1".into(),
486 kind: "prompt".into(),
487 message: "Name?".into(),
488 default_value: "Ada".into(),
489 }))),
490 error: None,
491 };
492 let json = serde_json::to_string(&envelope).unwrap();
493 assert_eq!(
494 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
495 envelope
496 );
497 }
498
499 #[test]
500 fn file_chooser_hook_envelope_round_trips() {
501 let envelope = PluginEnvelope {
502 ok: true,
503 result: Some(PluginResult::PollHook(HookResult::FileChooser(
504 FileChooserInfo {
505 file_chooser_id: "chooser-1".to_string(),
506 is_multiple: true,
507 note: "opened".to_string(),
508 },
509 ))),
510 error: None,
511 };
512
513 let json = serde_json::to_string(&envelope).unwrap();
514 assert_eq!(
515 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
516 envelope
517 );
518 }
519
520 #[test]
521 fn download_hook_envelope_round_trips() {
522 let envelope = PluginEnvelope {
523 ok: true,
524 result: Some(PluginResult::PollHook(HookResult::Download(DownloadInfo {
525 download_id: "download-1".to_string(),
526 url: "https://example.test/report.csv".to_string(),
527 suggested_filename: "report.csv".to_string(),
528 note: "started".to_string(),
529 }))),
530 error: None,
531 };
532
533 let json = serde_json::to_string(&envelope).unwrap();
534 assert_eq!(
535 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
536 envelope
537 );
538 }
539}
540
541#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
542pub struct CaptureInfo {
543 pub value: Option<String>,
544 pub checked: Option<bool>,
545 pub selected_options: Vec<CapturedOption>,
546 pub bounding_box: Option<BoundingBox>,
547}
548#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
549pub struct CapturedOption {
550 pub value: String,
551 pub label: String,
552 pub index: u32,
553}
554#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
555pub struct BoundingBox {
556 pub x: f64,
557 pub y: f64,
558 pub width: f64,
559 pub height: f64,
560}