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, Default, Serialize, Deserialize, PartialEq)]
244#[serde(rename_all = "camelCase")]
245pub struct InspectorBounds {
246 pub x: f64,
247 pub y: f64,
248 pub width: f64,
249 pub height: f64,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
253#[serde(rename_all = "camelCase")]
254pub struct InspectorNode {
255 pub role: String,
256 pub name: String,
257 pub automation_id: String,
258 pub class_name: String,
259 pub enabled: bool,
260 pub offscreen: bool,
261 pub bounds: InspectorBounds,
262 #[serde(default)]
263 pub properties: serde_json::Value,
264 #[serde(default)]
265 pub states: serde_json::Value,
266 pub children: Vec<InspectorNode>,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
270pub struct InspectorSnapshot {
271 pub version: u32,
272 pub platform: String,
273 pub backend: String,
274 pub root: InspectorNode,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
278pub struct ScreenshotInfo {
279 pub png_data: Vec<u8>,
280 pub note: String,
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
284#[serde(tag = "command", rename_all = "snake_case")]
285pub enum PluginCommand {
286 LaunchBrowser {
287 browser_kind: BrowserKind,
288 browser_binary: Option<String>,
289 },
290 ResolveFrame {
291 browser_session: BrowserSessionHandle,
292 page_session: PageSessionHandle,
293 css_selector: String,
294 timeout_ms: u64,
295 },
296 OpenPage {
297 browser_session: BrowserSessionHandle,
298 },
299 RegisterHook {
300 browser_session: BrowserSessionHandle,
301 page_session: PageSessionHandle,
302 hook_type: HookType,
303 },
304 PollHook {
305 browser_session: BrowserSessionHandle,
306 registration: HookRegistration,
307 },
308 HandleDialog {
309 browser_session: BrowserSessionHandle,
310 page_session: PageSessionHandle,
311 dialog_id: String,
312 accept: bool,
313 prompt_text: Option<String>,
314 },
315 SetFileChooserFiles {
316 browser_session: BrowserSessionHandle,
317 page_session: PageSessionHandle,
318 file_chooser_id: String,
319 files: Vec<String>,
320 },
321 SaveDownload {
322 browser_session: BrowserSessionHandle,
323 page_session: PageSessionHandle,
324 download_id: String,
325 path: String,
326 },
327 ClosePage {
328 browser_session: BrowserSessionHandle,
329 page_session: PageSessionHandle,
330 },
331 NavigatePage {
332 browser_session: BrowserSessionHandle,
333 page_session: PageSessionHandle,
334 url: String,
335 },
336 ClickElement {
337 browser_session: BrowserSessionHandle,
338 page_session: PageSessionHandle,
339 css_selector: String,
340 button: u8,
341 click_count: u32,
342 },
343 CountElements {
344 browser_session: BrowserSessionHandle,
345 page_session: PageSessionHandle,
346 css_selector: String,
347 },
348 HighlightElements {
349 browser_session: BrowserSessionHandle,
350 page_session: PageSessionHandle,
351 css_selector: String,
352 duration_ms: u64,
353 },
354 FocusElement {
355 browser_session: BrowserSessionHandle,
356 page_session: PageSessionHandle,
357 css_selector: String,
358 },
359 FillElement {
360 browser_session: BrowserSessionHandle,
361 page_session: PageSessionHandle,
362 css_selector: String,
363 value: String,
364 },
365 HoverElement {
366 browser_session: BrowserSessionHandle,
367 page_session: PageSessionHandle,
368 css_selector: String,
369 },
370 PressKey {
371 browser_session: BrowserSessionHandle,
372 page_session: PageSessionHandle,
373 css_selector: String,
374 key: String,
375 text: Option<String>,
376 },
377 Capture {
378 browser_session: BrowserSessionHandle,
379 page_session: PageSessionHandle,
380 kind: String,
381 css_selector: String,
382 attribute_name: String,
383 },
384 GetTextContent {
385 browser_session: BrowserSessionHandle,
386 page_session: PageSessionHandle,
387 css_selector: String,
388 },
389 GetInnerText {
390 browser_session: BrowserSessionHandle,
391 page_session: PageSessionHandle,
392 css_selector: String,
393 },
394 WaitForSelector {
395 browser_session: BrowserSessionHandle,
396 page_session: PageSessionHandle,
397 css_selector: String,
398 visible: bool,
399 },
400 AccessibilitySnapshot {
401 browser_session: BrowserSessionHandle,
402 page_session: PageSessionHandle,
403 format: String,
404 #[serde(default)]
405 mode: String,
406 },
407 Screenshot {
408 browser_session: BrowserSessionHandle,
409 page_session: PageSessionHandle,
410 full_page: bool,
411 },
412 OpenChromeWindow {
413 chrome_binary: Option<String>,
414 },
415 DiscoverInitialTab {
416 cdp_websocket_url: String,
417 },
418 OpenChromeTab {
419 cdp_websocket_url: String,
420 },
421 CloseBrowserProcess {
422 process_id: u32,
423 },
424 CloseChromeTab {
425 cdp_websocket_url: String,
426 target_id: String,
427 },
428 NavigateChromeTab {
429 cdp_websocket_url: String,
430 target_id: String,
431 url: String,
432 },
433 InjectChromiumBidiMapper {
434 cdp_websocket_url: String,
435 },
436 ResolveBidiContextForTab {
437 cdp_websocket_url: String,
438 mapper_target_id: Option<String>,
439 browsing_context_id: Option<String>,
440 url: Option<String>,
441 },
442}
443
444#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
445#[serde(tag = "result", rename_all = "snake_case")]
446pub enum PluginResult {
447 LaunchBrowser(BrowserLaunchInfo),
448 ResolveFrame(PageInfo),
449 OpenPage(PageInfo),
450 RegisterHook(HookRegistration),
451 PollHook(HookResult),
452 HandleDialog,
453 SetFileChooserFiles(FileChooserFilesSetInfo),
454 SaveDownload(DownloadSavedInfo),
455 ClosePage,
456 NavigatePage(TabNavigationInfo),
457 ClickElement(ClickInfo),
458 CountElements(ElementCountInfo),
459 HighlightElements(HighlightElementsInfo),
460 FocusElement(FocusInfo),
461 FillElement(FillInfo),
462 HoverElement(HoverInfo),
463 PressKey(PressKeyInfo),
464 Capture(CaptureInfo),
465 GetTextContent(TextInfo),
466 GetInnerText(TextInfo),
467 WaitForSelector(WaitForSelectorInfo),
468 AccessibilitySnapshot(AccessibilitySnapshotInfo),
469 Screenshot(ScreenshotInfo),
470 OpenChromeWindow(ChromeLaunchInfo),
471 DiscoverInitialTab(ChromeTabInfo),
472 OpenChromeTab(ChromeTabInfo),
473 CloseBrowserProcess,
474 CloseChromeTab,
475 NavigateChromeTab(TabNavigationInfo),
476 InjectChromiumBidiMapper(ChromiumBidiMapperInfo),
477 ResolveBidiContextForTab {
478 browsing_context_id: String,
479 mapper: ChromiumBidiMapperInfo,
480 },
481}
482
483#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
484pub struct PluginEnvelope {
485 pub ok: bool,
486 pub result: Option<PluginResult>,
487 pub error: Option<String>,
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493
494 #[test]
495 fn poll_hook_envelope_round_trips_without_duplicate_result_fields() {
496 let envelope = PluginEnvelope {
497 ok: true,
498 result: Some(PluginResult::PollHook(HookResult::NewPage(PageInfo {
499 note: "opened".to_string(),
500 page_session: PageSessionHandle::Chromium {
501 target_id: "target-2".to_string(),
502 browsing_context_id: None,
503 mapper_target_id: None,
504 },
505 }))),
506 error: None,
507 };
508
509 let json = serde_json::to_string(&envelope).unwrap();
510 assert_eq!(
511 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
512 envelope
513 );
514 }
515
516 #[test]
517 fn dialog_hook_envelope_round_trips() {
518 let envelope = PluginEnvelope {
519 ok: true,
520 result: Some(PluginResult::PollHook(HookResult::Dialog(DialogInfo {
521 dialog_id: "dialog-1".into(),
522 kind: "prompt".into(),
523 message: "Name?".into(),
524 default_value: "Ada".into(),
525 }))),
526 error: None,
527 };
528 let json = serde_json::to_string(&envelope).unwrap();
529 assert_eq!(
530 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
531 envelope
532 );
533 }
534
535 #[test]
536 fn file_chooser_hook_envelope_round_trips() {
537 let envelope = PluginEnvelope {
538 ok: true,
539 result: Some(PluginResult::PollHook(HookResult::FileChooser(
540 FileChooserInfo {
541 file_chooser_id: "chooser-1".to_string(),
542 is_multiple: true,
543 note: "opened".to_string(),
544 },
545 ))),
546 error: None,
547 };
548
549 let json = serde_json::to_string(&envelope).unwrap();
550 assert_eq!(
551 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
552 envelope
553 );
554 }
555
556 #[test]
557 fn download_hook_envelope_round_trips() {
558 let envelope = PluginEnvelope {
559 ok: true,
560 result: Some(PluginResult::PollHook(HookResult::Download(DownloadInfo {
561 download_id: "download-1".to_string(),
562 url: "https://example.test/report.csv".to_string(),
563 suggested_filename: "report.csv".to_string(),
564 note: "started".to_string(),
565 }))),
566 error: None,
567 };
568
569 let json = serde_json::to_string(&envelope).unwrap();
570 assert_eq!(
571 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
572 envelope
573 );
574 }
575}
576
577#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
578pub struct CaptureInfo {
579 pub value: Option<String>,
580 pub checked: Option<bool>,
581 pub selected_options: Vec<CapturedOption>,
582 pub bounding_box: Option<BoundingBox>,
583}
584#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
585pub struct CapturedOption {
586 pub value: String,
587 pub label: String,
588 pub index: u32,
589}
590#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
591pub struct BoundingBox {
592 pub x: f64,
593 pub y: f64,
594 pub width: f64,
595 pub height: f64,
596}