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 GetTextContent {
325 browser_session: BrowserSessionHandle,
326 page_session: PageSessionHandle,
327 css_selector: String,
328 },
329 GetInnerText {
330 browser_session: BrowserSessionHandle,
331 page_session: PageSessionHandle,
332 css_selector: String,
333 },
334 WaitForSelector {
335 browser_session: BrowserSessionHandle,
336 page_session: PageSessionHandle,
337 css_selector: String,
338 visible: bool,
339 },
340 AccessibilitySnapshot {
341 browser_session: BrowserSessionHandle,
342 page_session: PageSessionHandle,
343 format: String,
344 #[serde(default)]
345 mode: String,
346 },
347 Screenshot {
348 browser_session: BrowserSessionHandle,
349 page_session: PageSessionHandle,
350 full_page: bool,
351 },
352 OpenChromeWindow {
353 chrome_binary: Option<String>,
354 },
355 DiscoverInitialTab {
356 cdp_websocket_url: String,
357 },
358 OpenChromeTab {
359 cdp_websocket_url: String,
360 },
361 CloseBrowserProcess {
362 process_id: u32,
363 },
364 CloseChromeTab {
365 cdp_websocket_url: String,
366 target_id: String,
367 },
368 NavigateChromeTab {
369 cdp_websocket_url: String,
370 target_id: String,
371 url: String,
372 },
373 InjectChromiumBidiMapper {
374 cdp_websocket_url: String,
375 },
376 ResolveBidiContextForTab {
377 cdp_websocket_url: String,
378 mapper_target_id: Option<String>,
379 browsing_context_id: Option<String>,
380 url: Option<String>,
381 },
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
385#[serde(tag = "result", rename_all = "snake_case")]
386pub enum PluginResult {
387 LaunchBrowser(BrowserLaunchInfo),
388 ResolveFrame(PageInfo),
389 OpenPage(PageInfo),
390 RegisterHook(HookRegistration),
391 PollHook(HookResult),
392 SetFileChooserFiles(FileChooserFilesSetInfo),
393 SaveDownload(DownloadSavedInfo),
394 ClosePage,
395 NavigatePage(TabNavigationInfo),
396 ClickElement(ClickInfo),
397 CountElements(ElementCountInfo),
398 HighlightElements(HighlightElementsInfo),
399 FocusElement(FocusInfo),
400 FillElement(FillInfo),
401 HoverElement(HoverInfo),
402 PressKey(PressKeyInfo),
403 GetTextContent(TextInfo),
404 GetInnerText(TextInfo),
405 WaitForSelector(WaitForSelectorInfo),
406 AccessibilitySnapshot(AccessibilitySnapshotInfo),
407 Screenshot(ScreenshotInfo),
408 OpenChromeWindow(ChromeLaunchInfo),
409 DiscoverInitialTab(ChromeTabInfo),
410 OpenChromeTab(ChromeTabInfo),
411 CloseBrowserProcess,
412 CloseChromeTab,
413 NavigateChromeTab(TabNavigationInfo),
414 InjectChromiumBidiMapper(ChromiumBidiMapperInfo),
415 ResolveBidiContextForTab {
416 browsing_context_id: String,
417 mapper: ChromiumBidiMapperInfo,
418 },
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
422pub struct PluginEnvelope {
423 pub ok: bool,
424 pub result: Option<PluginResult>,
425 pub error: Option<String>,
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431
432 #[test]
433 fn poll_hook_envelope_round_trips_without_duplicate_result_fields() {
434 let envelope = PluginEnvelope {
435 ok: true,
436 result: Some(PluginResult::PollHook(HookResult::NewPage(PageInfo {
437 note: "opened".to_string(),
438 page_session: PageSessionHandle::Chromium {
439 target_id: "target-2".to_string(),
440 browsing_context_id: None,
441 mapper_target_id: None,
442 },
443 }))),
444 error: None,
445 };
446
447 let json = serde_json::to_string(&envelope).unwrap();
448 assert_eq!(
449 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
450 envelope
451 );
452 }
453
454 #[test]
455 fn file_chooser_hook_envelope_round_trips() {
456 let envelope = PluginEnvelope {
457 ok: true,
458 result: Some(PluginResult::PollHook(HookResult::FileChooser(
459 FileChooserInfo {
460 file_chooser_id: "chooser-1".to_string(),
461 is_multiple: true,
462 note: "opened".to_string(),
463 },
464 ))),
465 error: None,
466 };
467
468 let json = serde_json::to_string(&envelope).unwrap();
469 assert_eq!(
470 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
471 envelope
472 );
473 }
474
475 #[test]
476 fn download_hook_envelope_round_trips() {
477 let envelope = PluginEnvelope {
478 ok: true,
479 result: Some(PluginResult::PollHook(HookResult::Download(DownloadInfo {
480 download_id: "download-1".to_string(),
481 url: "https://example.test/report.csv".to_string(),
482 suggested_filename: "report.csv".to_string(),
483 note: "started".to_string(),
484 }))),
485 error: None,
486 };
487
488 let json = serde_json::to_string(&envelope).unwrap();
489 assert_eq!(
490 serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
491 envelope
492 );
493 }
494}