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 button: u8,
307 click_count: u32,
308 },
309 CountElements {
310 browser_session: BrowserSessionHandle,
311 page_session: PageSessionHandle,
312 css_selector: String,
313 },
314 HighlightElements {
315 browser_session: BrowserSessionHandle,
316 page_session: PageSessionHandle,
317 css_selector: String,
318 duration_ms: u64,
319 },
320 FocusElement {
321 browser_session: BrowserSessionHandle,
322 page_session: PageSessionHandle,
323 css_selector: String,
324 },
325 FillElement {
326 browser_session: BrowserSessionHandle,
327 page_session: PageSessionHandle,
328 css_selector: String,
329 value: String,
330 },
331 HoverElement {
332 browser_session: BrowserSessionHandle,
333 page_session: PageSessionHandle,
334 css_selector: String,
335 },
336 PressKey {
337 browser_session: BrowserSessionHandle,
338 page_session: PageSessionHandle,
339 css_selector: String,
340 key: String,
341 text: Option<String>,
342 },
343 Capture {
344 browser_session: BrowserSessionHandle,
345 page_session: PageSessionHandle,
346 kind: String,
347 css_selector: String,
348 attribute_name: String,
349 },
350 GetTextContent {
351 browser_session: BrowserSessionHandle,
352 page_session: PageSessionHandle,
353 css_selector: String,
354 },
355 GetInnerText {
356 browser_session: BrowserSessionHandle,
357 page_session: PageSessionHandle,
358 css_selector: String,
359 },
360 WaitForSelector {
361 browser_session: BrowserSessionHandle,
362 page_session: PageSessionHandle,
363 css_selector: String,
364 visible: bool,
365 },
366 AccessibilitySnapshot {
367 browser_session: BrowserSessionHandle,
368 page_session: PageSessionHandle,
369 format: String,
370 #[serde(default)]
371 mode: String,
372 },
373 Screenshot {
374 browser_session: BrowserSessionHandle,
375 page_session: PageSessionHandle,
376 full_page: bool,
377 },
378 OpenChromeWindow {
379 chrome_binary: Option<String>,
380 },
381 DiscoverInitialTab {
382 cdp_websocket_url: String,
383 },
384 OpenChromeTab {
385 cdp_websocket_url: String,
386 },
387 CloseBrowserProcess {
388 process_id: u32,
389 },
390 CloseChromeTab {
391 cdp_websocket_url: String,
392 target_id: String,
393 },
394 NavigateChromeTab {
395 cdp_websocket_url: String,
396 target_id: String,
397 url: String,
398 },
399 InjectChromiumBidiMapper {
400 cdp_websocket_url: String,
401 },
402 ResolveBidiContextForTab {
403 cdp_websocket_url: String,
404 mapper_target_id: Option<String>,
405 browsing_context_id: Option<String>,
406 url: Option<String>,
407 },
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
411#[serde(tag = "result", rename_all = "snake_case")]
412pub enum PluginResult {
413 LaunchBrowser(BrowserLaunchInfo),
414 ResolveFrame(PageInfo),
415 OpenPage(PageInfo),
416 RegisterHook(HookRegistration),
417 PollHook(HookResult),
418 HandleDialog,
419 SetFileChooserFiles(FileChooserFilesSetInfo),
420 SaveDownload(DownloadSavedInfo),
421 ClosePage,
422 NavigatePage(TabNavigationInfo),
423 ClickElement(ClickInfo),
424 CountElements(ElementCountInfo),
425 HighlightElements(HighlightElementsInfo),
426 FocusElement(FocusInfo),
427 FillElement(FillInfo),
428 HoverElement(HoverInfo),
429 PressKey(PressKeyInfo),
430 Capture(CaptureInfo),
431 GetTextContent(TextInfo),
432 GetInnerText(TextInfo),
433 WaitForSelector(WaitForSelectorInfo),
434 AccessibilitySnapshot(AccessibilitySnapshotInfo),
435 Screenshot(ScreenshotInfo),
436 OpenChromeWindow(ChromeLaunchInfo),
437 DiscoverInitialTab(ChromeTabInfo),
438 OpenChromeTab(ChromeTabInfo),
439 CloseBrowserProcess,
440 CloseChromeTab,
441 NavigateChromeTab(TabNavigationInfo),
442 InjectChromiumBidiMapper(ChromiumBidiMapperInfo),
443 ResolveBidiContextForTab {
444 browsing_context_id: String,
445 mapper: ChromiumBidiMapperInfo,
446 },
447}
448
449#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
450pub struct PluginEnvelope {
451 pub ok: bool,
452 pub result: Option<PluginResult>,
453 pub error: Option<String>,
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 #[test]
461 fn poll_hook_envelope_round_trips_without_duplicate_result_fields() {
462 let envelope = PluginEnvelope {
463 ok: true,
464 result: Some(PluginResult::PollHook(HookResult::NewPage(PageInfo {
465 note: "opened".to_string(),
466 page_session: PageSessionHandle::Chromium {
467 target_id: "target-2".to_string(),
468 browsing_context_id: None,
469 mapper_target_id: None,
470 },
471 }))),
472 error: None,
473 };
474
475 let json = serde_json::to_string(&envelope).unwrap();
476 assert_eq!(
477 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
478 envelope
479 );
480 }
481
482 #[test]
483 fn dialog_hook_envelope_round_trips() {
484 let envelope = PluginEnvelope {
485 ok: true,
486 result: Some(PluginResult::PollHook(HookResult::Dialog(DialogInfo {
487 dialog_id: "dialog-1".into(),
488 kind: "prompt".into(),
489 message: "Name?".into(),
490 default_value: "Ada".into(),
491 }))),
492 error: None,
493 };
494 let json = serde_json::to_string(&envelope).unwrap();
495 assert_eq!(
496 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
497 envelope
498 );
499 }
500
501 #[test]
502 fn file_chooser_hook_envelope_round_trips() {
503 let envelope = PluginEnvelope {
504 ok: true,
505 result: Some(PluginResult::PollHook(HookResult::FileChooser(
506 FileChooserInfo {
507 file_chooser_id: "chooser-1".to_string(),
508 is_multiple: true,
509 note: "opened".to_string(),
510 },
511 ))),
512 error: None,
513 };
514
515 let json = serde_json::to_string(&envelope).unwrap();
516 assert_eq!(
517 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
518 envelope
519 );
520 }
521
522 #[test]
523 fn download_hook_envelope_round_trips() {
524 let envelope = PluginEnvelope {
525 ok: true,
526 result: Some(PluginResult::PollHook(HookResult::Download(DownloadInfo {
527 download_id: "download-1".to_string(),
528 url: "https://example.test/report.csv".to_string(),
529 suggested_filename: "report.csv".to_string(),
530 note: "started".to_string(),
531 }))),
532 error: None,
533 };
534
535 let json = serde_json::to_string(&envelope).unwrap();
536 assert_eq!(
537 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
538 envelope
539 );
540 }
541}
542
543#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
544pub struct CaptureInfo {
545 pub value: Option<String>,
546 pub checked: Option<bool>,
547 pub selected_options: Vec<CapturedOption>,
548 pub bounding_box: Option<BoundingBox>,
549}
550#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
551pub struct CapturedOption {
552 pub value: String,
553 pub label: String,
554 pub index: u32,
555}
556#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
557pub struct BoundingBox {
558 pub x: f64,
559 pub y: f64,
560 pub width: f64,
561 pub height: f64,
562}