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