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 OpenPage {
247 browser_session: BrowserSessionHandle,
248 },
249 RegisterHook {
250 browser_session: BrowserSessionHandle,
251 page_session: PageSessionHandle,
252 hook_type: HookType,
253 },
254 PollHook {
255 browser_session: BrowserSessionHandle,
256 registration: HookRegistration,
257 },
258 SetFileChooserFiles {
259 browser_session: BrowserSessionHandle,
260 page_session: PageSessionHandle,
261 file_chooser_id: String,
262 files: Vec<String>,
263 },
264 SaveDownload {
265 browser_session: BrowserSessionHandle,
266 page_session: PageSessionHandle,
267 download_id: String,
268 path: String,
269 },
270 ClosePage {
271 browser_session: BrowserSessionHandle,
272 page_session: PageSessionHandle,
273 },
274 NavigatePage {
275 browser_session: BrowserSessionHandle,
276 page_session: PageSessionHandle,
277 url: String,
278 },
279 ClickElement {
280 browser_session: BrowserSessionHandle,
281 page_session: PageSessionHandle,
282 css_selector: String,
283 },
284 CountElements {
285 browser_session: BrowserSessionHandle,
286 page_session: PageSessionHandle,
287 css_selector: String,
288 },
289 HighlightElements {
290 browser_session: BrowserSessionHandle,
291 page_session: PageSessionHandle,
292 css_selector: String,
293 duration_ms: u64,
294 },
295 FocusElement {
296 browser_session: BrowserSessionHandle,
297 page_session: PageSessionHandle,
298 css_selector: String,
299 },
300 FillElement {
301 browser_session: BrowserSessionHandle,
302 page_session: PageSessionHandle,
303 css_selector: String,
304 value: String,
305 },
306 HoverElement {
307 browser_session: BrowserSessionHandle,
308 page_session: PageSessionHandle,
309 css_selector: String,
310 },
311 PressKey {
312 browser_session: BrowserSessionHandle,
313 page_session: PageSessionHandle,
314 css_selector: String,
315 key: String,
316 text: Option<String>,
317 },
318 GetTextContent {
319 browser_session: BrowserSessionHandle,
320 page_session: PageSessionHandle,
321 css_selector: String,
322 },
323 GetInnerText {
324 browser_session: BrowserSessionHandle,
325 page_session: PageSessionHandle,
326 css_selector: String,
327 },
328 WaitForSelector {
329 browser_session: BrowserSessionHandle,
330 page_session: PageSessionHandle,
331 css_selector: String,
332 visible: bool,
333 },
334 AccessibilitySnapshot {
335 browser_session: BrowserSessionHandle,
336 page_session: PageSessionHandle,
337 format: String,
338 #[serde(default)]
339 mode: String,
340 },
341 Screenshot {
342 browser_session: BrowserSessionHandle,
343 page_session: PageSessionHandle,
344 full_page: bool,
345 },
346 OpenChromeWindow {
347 chrome_binary: Option<String>,
348 },
349 DiscoverInitialTab {
350 cdp_websocket_url: String,
351 },
352 OpenChromeTab {
353 cdp_websocket_url: String,
354 },
355 CloseBrowserProcess {
356 process_id: u32,
357 },
358 CloseChromeTab {
359 cdp_websocket_url: String,
360 target_id: String,
361 },
362 NavigateChromeTab {
363 cdp_websocket_url: String,
364 target_id: String,
365 url: String,
366 },
367 InjectChromiumBidiMapper {
368 cdp_websocket_url: String,
369 },
370 ResolveBidiContextForTab {
371 cdp_websocket_url: String,
372 mapper_target_id: Option<String>,
373 browsing_context_id: Option<String>,
374 url: Option<String>,
375 },
376}
377
378#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
379#[serde(tag = "result", rename_all = "snake_case")]
380pub enum PluginResult {
381 LaunchBrowser(BrowserLaunchInfo),
382 OpenPage(PageInfo),
383 RegisterHook(HookRegistration),
384 PollHook(HookResult),
385 SetFileChooserFiles(FileChooserFilesSetInfo),
386 SaveDownload(DownloadSavedInfo),
387 ClosePage,
388 NavigatePage(TabNavigationInfo),
389 ClickElement(ClickInfo),
390 CountElements(ElementCountInfo),
391 HighlightElements(HighlightElementsInfo),
392 FocusElement(FocusInfo),
393 FillElement(FillInfo),
394 HoverElement(HoverInfo),
395 PressKey(PressKeyInfo),
396 GetTextContent(TextInfo),
397 GetInnerText(TextInfo),
398 WaitForSelector(WaitForSelectorInfo),
399 AccessibilitySnapshot(AccessibilitySnapshotInfo),
400 Screenshot(ScreenshotInfo),
401 OpenChromeWindow(ChromeLaunchInfo),
402 DiscoverInitialTab(ChromeTabInfo),
403 OpenChromeTab(ChromeTabInfo),
404 CloseBrowserProcess,
405 CloseChromeTab,
406 NavigateChromeTab(TabNavigationInfo),
407 InjectChromiumBidiMapper(ChromiumBidiMapperInfo),
408 ResolveBidiContextForTab {
409 browsing_context_id: String,
410 mapper: ChromiumBidiMapperInfo,
411 },
412}
413
414#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
415pub struct PluginEnvelope {
416 pub ok: bool,
417 pub result: Option<PluginResult>,
418 pub error: Option<String>,
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 #[test]
426 fn poll_hook_envelope_round_trips_without_duplicate_result_fields() {
427 let envelope = PluginEnvelope {
428 ok: true,
429 result: Some(PluginResult::PollHook(HookResult::NewPage(PageInfo {
430 note: "opened".to_string(),
431 page_session: PageSessionHandle::Chromium {
432 target_id: "target-2".to_string(),
433 browsing_context_id: None,
434 mapper_target_id: None,
435 },
436 }))),
437 error: None,
438 };
439
440 let json = serde_json::to_string(&envelope).unwrap();
441 assert_eq!(
442 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
443 envelope
444 );
445 }
446
447 #[test]
448 fn file_chooser_hook_envelope_round_trips() {
449 let envelope = PluginEnvelope {
450 ok: true,
451 result: Some(PluginResult::PollHook(HookResult::FileChooser(
452 FileChooserInfo {
453 file_chooser_id: "chooser-1".to_string(),
454 is_multiple: true,
455 note: "opened".to_string(),
456 },
457 ))),
458 error: None,
459 };
460
461 let json = serde_json::to_string(&envelope).unwrap();
462 assert_eq!(
463 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
464 envelope
465 );
466 }
467
468 #[test]
469 fn download_hook_envelope_round_trips() {
470 let envelope = PluginEnvelope {
471 ok: true,
472 result: Some(PluginResult::PollHook(HookResult::Download(DownloadInfo {
473 download_id: "download-1".to_string(),
474 url: "https://example.test/report.csv".to_string(),
475 suggested_filename: "report.csv".to_string(),
476 note: "started".to_string(),
477 }))),
478 error: None,
479 };
480
481 let json = serde_json::to_string(&envelope).unwrap();
482 assert_eq!(
483 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
484 envelope
485 );
486 }
487}