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 NewPage,
109 FileChooser,
110 Download,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
114pub struct HookRegistration {
115 pub opaque_state: String,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
119#[serde(tag = "hook_type", content = "value", rename_all = "snake_case")]
120pub enum HookResult {
121 NewPage(PageInfo),
122 FileChooser(FileChooserInfo),
123 Download(DownloadInfo),
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
127pub struct FileChooserInfo {
128 pub file_chooser_id: String,
129 pub is_multiple: bool,
130 pub note: String,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
134pub struct FileChooserFilesSetInfo {
135 pub file_chooser_id: String,
136 pub files: Vec<String>,
137 pub note: String,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
141pub struct DownloadInfo {
142 pub download_id: String,
143 pub url: String,
144 pub suggested_filename: String,
145 pub note: String,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
149pub struct DownloadSavedInfo {
150 pub download_id: String,
151 pub path: String,
152 pub note: String,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
156pub struct AutomationSessionInfo {
157 pub bidi_session_id: String,
158 pub note: String,
159 pub mapper_target_id: Option<String>,
160 pub mapper_session_id: Option<String>,
161 pub package_version: Option<String>,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
165pub struct ClickInfo {
166 pub css_selector: String,
167 pub note: String,
168 pub bidi_session_id: String,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
172pub struct ElementCountInfo {
173 pub css_selector: String,
174 pub count: u32,
175 pub note: String,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
179pub struct HighlightElementsInfo {
180 pub css_selector: String,
181 pub count: u32,
182 pub note: String,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
186pub struct FocusInfo {
187 pub css_selector: String,
188 pub note: String,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
192pub struct FillInfo {
193 pub css_selector: String,
194 pub value: String,
195 pub note: String,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
199pub struct HoverInfo {
200 pub css_selector: String,
201 pub note: String,
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
205pub struct PressKeyInfo {
206 pub css_selector: String,
207 pub key: String,
208 pub note: String,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
212pub struct TextInfo {
213 pub css_selector: String,
214 pub text: String,
215 pub note: String,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
219pub struct WaitForSelectorInfo {
220 pub css_selector: String,
221 pub visible: bool,
222 pub note: String,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
226pub struct AccessibilitySnapshotInfo {
227 pub snapshot: String,
228 pub format: String,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
232pub struct ScreenshotInfo {
233 pub png_data: Vec<u8>,
234 pub note: String,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
238#[serde(tag = "command", rename_all = "snake_case")]
239pub enum PluginCommand {
240 LaunchBrowser {
241 browser_kind: BrowserKind,
242 browser_binary: Option<String>,
243 },
244 OpenPage {
245 browser_session: BrowserSessionHandle,
246 },
247 RegisterHook {
248 browser_session: BrowserSessionHandle,
249 page_session: PageSessionHandle,
250 hook_type: HookType,
251 },
252 PollHook {
253 browser_session: BrowserSessionHandle,
254 registration: HookRegistration,
255 },
256 SetFileChooserFiles {
257 browser_session: BrowserSessionHandle,
258 page_session: PageSessionHandle,
259 file_chooser_id: String,
260 files: Vec<String>,
261 },
262 SaveDownload {
263 browser_session: BrowserSessionHandle,
264 page_session: PageSessionHandle,
265 download_id: String,
266 path: String,
267 },
268 ClosePage {
269 browser_session: BrowserSessionHandle,
270 page_session: PageSessionHandle,
271 },
272 NavigatePage {
273 browser_session: BrowserSessionHandle,
274 page_session: PageSessionHandle,
275 url: String,
276 },
277 ClickElement {
278 browser_session: BrowserSessionHandle,
279 page_session: PageSessionHandle,
280 css_selector: String,
281 },
282 CountElements {
283 browser_session: BrowserSessionHandle,
284 page_session: PageSessionHandle,
285 css_selector: String,
286 },
287 HighlightElements {
288 browser_session: BrowserSessionHandle,
289 page_session: PageSessionHandle,
290 css_selector: String,
291 duration_ms: u64,
292 },
293 FocusElement {
294 browser_session: BrowserSessionHandle,
295 page_session: PageSessionHandle,
296 css_selector: String,
297 },
298 FillElement {
299 browser_session: BrowserSessionHandle,
300 page_session: PageSessionHandle,
301 css_selector: String,
302 value: String,
303 },
304 HoverElement {
305 browser_session: BrowserSessionHandle,
306 page_session: PageSessionHandle,
307 css_selector: String,
308 },
309 PressKey {
310 browser_session: BrowserSessionHandle,
311 page_session: PageSessionHandle,
312 css_selector: String,
313 key: String,
314 text: Option<String>,
315 },
316 GetTextContent {
317 browser_session: BrowserSessionHandle,
318 page_session: PageSessionHandle,
319 css_selector: String,
320 },
321 GetInnerText {
322 browser_session: BrowserSessionHandle,
323 page_session: PageSessionHandle,
324 css_selector: String,
325 },
326 WaitForSelector {
327 browser_session: BrowserSessionHandle,
328 page_session: PageSessionHandle,
329 css_selector: String,
330 visible: bool,
331 },
332 AccessibilitySnapshot {
333 browser_session: BrowserSessionHandle,
334 page_session: PageSessionHandle,
335 format: String,
336 #[serde(default)]
337 mode: String,
338 },
339 Screenshot {
340 browser_session: BrowserSessionHandle,
341 page_session: PageSessionHandle,
342 full_page: bool,
343 },
344 OpenChromeWindow {
345 chrome_binary: Option<String>,
346 },
347 DiscoverInitialTab {
348 cdp_websocket_url: String,
349 },
350 OpenChromeTab {
351 cdp_websocket_url: String,
352 },
353 CloseBrowserProcess {
354 process_id: u32,
355 },
356 CloseChromeTab {
357 cdp_websocket_url: String,
358 target_id: String,
359 },
360 NavigateChromeTab {
361 cdp_websocket_url: String,
362 target_id: String,
363 url: String,
364 },
365 InjectChromiumBidiMapper {
366 cdp_websocket_url: String,
367 },
368 ResolveBidiContextForTab {
369 cdp_websocket_url: String,
370 mapper_target_id: Option<String>,
371 browsing_context_id: Option<String>,
372 url: Option<String>,
373 },
374 ClickElementViaCdp {
375 cdp_websocket_url: String,
376 target_id: String,
377 css_selector: String,
378 },
379 CountElementsViaCdp {
380 cdp_websocket_url: String,
381 target_id: String,
382 css_selector: String,
383 },
384 HighlightElementsViaCdp {
385 cdp_websocket_url: String,
386 target_id: String,
387 css_selector: String,
388 duration_ms: u64,
389 },
390 FocusElementViaCdp {
391 cdp_websocket_url: String,
392 target_id: String,
393 css_selector: String,
394 },
395 FillElementViaCdp {
396 cdp_websocket_url: String,
397 target_id: String,
398 css_selector: String,
399 value: String,
400 },
401 HoverElementViaCdp {
402 cdp_websocket_url: String,
403 target_id: String,
404 css_selector: String,
405 },
406 PressKeyViaCdp {
407 cdp_websocket_url: String,
408 target_id: String,
409 css_selector: String,
410 key: String,
411 text: Option<String>,
412 },
413 GetTextContentViaCdp {
414 cdp_websocket_url: String,
415 target_id: String,
416 css_selector: String,
417 },
418 GetInnerTextViaCdp {
419 cdp_websocket_url: String,
420 target_id: String,
421 css_selector: String,
422 },
423 WaitForSelectorViaCdp {
424 cdp_websocket_url: String,
425 target_id: String,
426 css_selector: String,
427 visible: bool,
428 },
429 ScreenshotViaCdp {
430 cdp_websocket_url: String,
431 target_id: String,
432 },
433}
434
435#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
436#[serde(tag = "result", rename_all = "snake_case")]
437pub enum PluginResult {
438 LaunchBrowser(BrowserLaunchInfo),
439 OpenPage(PageInfo),
440 RegisterHook(HookRegistration),
441 PollHook(HookResult),
442 SetFileChooserFiles(FileChooserFilesSetInfo),
443 SaveDownload(DownloadSavedInfo),
444 ClosePage,
445 NavigatePage(TabNavigationInfo),
446 ClickElement(ClickInfo),
447 CountElements(ElementCountInfo),
448 HighlightElements(HighlightElementsInfo),
449 FocusElement(FocusInfo),
450 FillElement(FillInfo),
451 HoverElement(HoverInfo),
452 PressKey(PressKeyInfo),
453 GetTextContent(TextInfo),
454 GetInnerText(TextInfo),
455 WaitForSelector(WaitForSelectorInfo),
456 AccessibilitySnapshot(AccessibilitySnapshotInfo),
457 Screenshot(ScreenshotInfo),
458 OpenChromeWindow(ChromeLaunchInfo),
459 DiscoverInitialTab(ChromeTabInfo),
460 OpenChromeTab(ChromeTabInfo),
461 CloseBrowserProcess,
462 CloseChromeTab,
463 NavigateChromeTab(TabNavigationInfo),
464 InjectChromiumBidiMapper(ChromiumBidiMapperInfo),
465 ResolveBidiContextForTab {
466 browsing_context_id: String,
467 mapper: ChromiumBidiMapperInfo,
468 },
469 ClickElementViaCdp(ClickInfo),
470 CountElementsViaCdp(ElementCountInfo),
471 HighlightElementsViaCdp(HighlightElementsInfo),
472 FocusElementViaCdp(FocusInfo),
473 FillElementViaCdp(FillInfo),
474 HoverElementViaCdp(HoverInfo),
475 PressKeyViaCdp(PressKeyInfo),
476 GetTextContentViaCdp(TextInfo),
477 GetInnerTextViaCdp(TextInfo),
478 WaitForSelectorViaCdp(WaitForSelectorInfo),
479 ScreenshotViaCdp(ScreenshotInfo),
480}
481
482#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
483pub struct PluginEnvelope {
484 pub ok: bool,
485 pub result: Option<PluginResult>,
486 pub error: Option<String>,
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492
493 #[test]
494 fn poll_hook_envelope_round_trips_without_duplicate_result_fields() {
495 let envelope = PluginEnvelope {
496 ok: true,
497 result: Some(PluginResult::PollHook(HookResult::NewPage(PageInfo {
498 note: "opened".to_string(),
499 page_session: PageSessionHandle::Chromium {
500 target_id: "target-2".to_string(),
501 browsing_context_id: None,
502 mapper_target_id: None,
503 },
504 }))),
505 error: None,
506 };
507
508 let json = serde_json::to_string(&envelope).unwrap();
509 assert_eq!(
510 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
511 envelope
512 );
513 }
514
515 #[test]
516 fn file_chooser_hook_envelope_round_trips() {
517 let envelope = PluginEnvelope {
518 ok: true,
519 result: Some(PluginResult::PollHook(HookResult::FileChooser(
520 FileChooserInfo {
521 file_chooser_id: "chooser-1".to_string(),
522 is_multiple: true,
523 note: "opened".to_string(),
524 },
525 ))),
526 error: None,
527 };
528
529 let json = serde_json::to_string(&envelope).unwrap();
530 assert_eq!(
531 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
532 envelope
533 );
534 }
535
536 #[test]
537 fn download_hook_envelope_round_trips() {
538 let envelope = PluginEnvelope {
539 ok: true,
540 result: Some(PluginResult::PollHook(HookResult::Download(DownloadInfo {
541 download_id: "download-1".to_string(),
542 url: "https://example.test/report.csv".to_string(),
543 suggested_filename: "report.csv".to_string(),
544 note: "started".to_string(),
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}