1#![allow(non_upper_case_globals)]
2
3use std::{
4 ffi::{CStr, CString},
5 os::raw::c_char,
6 ptr,
7 time::Duration,
8};
9
10use cbf::data::{edit::EditAction, window_open::WindowOpenResponse};
11use cbf_chrome_sys::{
12 bridge::{BridgeLibrary, BridgeLoadError, bridge},
13 ffi::*,
14};
15use tracing::warn;
16
17use super::map::{
18 ime_range_to_ffi, key_event_type_to_ffi, mouse_button_to_ffi, mouse_event_type_to_ffi,
19 parse_event, parse_extension_list, pointer_type_to_ffi, scroll_granularity_to_ffi,
20 to_ffi_ime_text_spans,
21};
22use super::utils::{c_string_to_string, to_optional_cstring};
23use super::{BridgeError, IpcEvent};
24use crate::data::{
25 background::ChromeBackgroundPolicy,
26 browsing_context_open::ChromeBrowsingContextOpenResponse,
27 custom_scheme::{ChromeCustomSchemeResponse, ChromeCustomSchemeResponseResult},
28 download::ChromeDownloadId,
29 drag::{
30 ChromeDragData, ChromeDragDrop, ChromeDragUpdate, ChromeExternalDragDrop,
31 ChromeExternalDragEnter, ChromeExternalDragUpdate,
32 },
33 execution::ChromeTabExecutionState,
34 extension::ChromeExtensionInfo,
35 find::{ChromeFindInPageOptions, ChromeStopFindAction},
36 ids::{PopupId, TabId},
37 ime::{
38 ChromeConfirmCompositionBehavior, ChromeImeCommitText, ChromeImeComposition,
39 ChromeTransientImeCommitText, ChromeTransientImeComposition,
40 },
41 input::{ChromeKeyEvent, ChromeMouseWheelEvent},
42 ipc::{TabIpcConfig, TabIpcErrorCode, TabIpcMessage, TabIpcMessageType, TabIpcPayload},
43 mouse::ChromeMouseEvent,
44 navigation::ChromeNavigationEntryId,
45 policy::{ChromeBrowsingContextPolicy, ChromeCapabilityPolicy, ChromeIpcPolicy},
46 profile::ChromeProfileInfo,
47 prompt_ui::{PromptUiId, PromptUiResponse},
48 visibility::ChromeTabVisibility,
49};
50
51fn execution_state_to_ffi(state: ChromeTabExecutionState) -> u8 {
52 match state {
53 ChromeTabExecutionState::Running => CbfTabExecutionState_kCbfTabExecutionStateRunning as u8,
54 ChromeTabExecutionState::Suspended => {
55 CbfTabExecutionState_kCbfTabExecutionStateSuspended as u8
56 }
57 }
58}
59
60pub struct IpcClient {
62 inner: *mut CbfBridgeClientHandle,
63}
64
65#[derive(Debug, Clone, Copy)]
66pub(crate) struct IpcEventWaitHandle {
67 inner: *mut CbfBridgeClientHandle,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum EventWaitResult {
72 EventAvailable,
73 TimedOut,
74 Disconnected,
75 Closed,
76}
77
78unsafe impl Send for IpcClient {}
83unsafe impl Send for IpcEventWaitHandle {}
87unsafe impl Sync for IpcEventWaitHandle {}
90
91macro_rules! bridge_call {
92 ($method:ident ( $($arg:expr),* $(,)? )) => {{
93 let bridge = bridge_api()?;
94 unsafe { bridge.$method($($arg),*) }
95 }};
96}
97
98impl std::fmt::Debug for IpcClient {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 f.debug_struct("IpcClient")
101 .field("inner", &format!("{:p}", self.inner))
102 .finish()
103 }
104}
105
106impl IpcClient {
107 pub fn set_base_bundle_id(bundle_id: &str) -> Result<(), BridgeError> {
111 let bundle_id = CString::new(bundle_id).map_err(|_| BridgeError::InvalidInput)?;
112 bridge_call!(cbf_bridge_set_base_bundle_id(bundle_id.as_ptr()));
113
114 Ok(())
115 }
116
117 pub fn prepare_channel_and_lock() -> Result<(i32, String), BridgeError> {
126 let mut buf = [0u8; 512];
127
128 let fd = {
129 bridge_call!(cbf_bridge_init());
130 bridge_call!(cbf_bridge_prepare_channel_and_lock(
131 buf.as_mut_ptr() as *mut std::os::raw::c_char,
132 buf.len() as i32,
133 ))
134 };
135
136 let switch_arg = parse_channel_switch_arg(&buf)?;
137
138 Ok((fd, switch_arg))
139 }
140
141 pub fn pass_child_pid_and_unlock(pid: u32) {
147 if let Err(err) = bridge_api()
148 .map(|bridge| unsafe { bridge.cbf_bridge_pass_child_pid_and_unlock(pid as i64) })
149 {
150 warn!(error = ?err, "failed to pass child pid to bridge");
151 }
152 }
153
154 pub fn abort_channel_launch() {
156 if let Err(err) =
157 bridge_api().map(|bridge| unsafe { bridge.cbf_bridge_abort_channel_launch() })
158 {
159 warn!(error = ?err, "failed to abort bridge child launch");
160 }
161 }
162
163 pub unsafe fn connect_inherited(
175 inner: *mut CbfBridgeClientHandle,
176 ) -> Result<Self, BridgeError> {
177 if inner.is_null() {
178 return Err(BridgeError::InvalidState);
179 }
180
181 let connected = bridge_call!(cbf_bridge_client_connect_inherited(inner));
182
183 if !connected {
184 warn!(
185 result = "err",
186 error = "ipc_connect_inherited_failed",
187 "IPC inherited connect failed"
188 );
189 cleanup_bridge_call("destroy bridge client after connect failure", |bridge| {
190 unsafe { bridge.cbf_bridge_client_destroy(inner) };
191 });
192
193 return Err(BridgeError::ConnectionFailed);
194 }
195
196 Ok(Self { inner })
197 }
198
199 pub fn authenticate(&self, token: &str) -> Result<(), BridgeError> {
203 if self.inner.is_null() {
204 return Err(BridgeError::InvalidState);
205 }
206
207 let token = CString::new(token).map_err(|_| BridgeError::InvalidInput)?;
208 authentication_result(bridge_call!(cbf_bridge_client_authenticate(
209 self.inner,
210 token.as_ptr()
211 )))
212 }
213
214 pub fn wait_for_event(
216 &self,
217 timeout: Option<Duration>,
218 ) -> Result<EventWaitResult, BridgeError> {
219 wait_for_event_inner(self.inner, timeout)
220 }
221
222 pub(crate) fn event_wait_handle(&self) -> IpcEventWaitHandle {
223 IpcEventWaitHandle { inner: self.inner }
224 }
225
226 pub fn poll_event(&mut self) -> Option<Result<IpcEvent, BridgeError>> {
228 if self.inner.is_null() {
229 return None;
230 }
231
232 let mut event = CbfBridgeEvent::default();
233 let polled = bridge_api()
234 .map(|bridge| unsafe { bridge.cbf_bridge_client_poll_event(self.inner, &mut event) })
235 .ok()?;
236
237 if !polled {
238 return None;
239 }
240
241 let parsed = parse_event(event);
242 cleanup_bridge_call("free bridge event", |bridge| {
243 unsafe { bridge.cbf_bridge_event_free(&mut event) };
244 });
245
246 if let Err(err) = &parsed {
247 warn!(
248 result = "err",
249 error = "ipc_event_parse_failed",
250 err = ?err,
251 "IPC event parse failed"
252 );
253 }
254
255 Some(parsed)
256 }
257
258 pub fn list_profiles(&mut self) -> Result<Vec<ChromeProfileInfo>, BridgeError> {
260 self.ensure_ready()?;
261
262 let mut list = CbfProfileList::default();
263 if !bridge_call!(cbf_bridge_client_get_profiles(self.inner, &mut list)) {
264 return Err(BridgeError::OperationFailed {
265 operation: "list_profiles",
266 });
267 }
268
269 let profiles = if list.len == 0 || list.profiles.is_null() {
270 &[]
271 } else {
272 unsafe { std::slice::from_raw_parts(list.profiles, list.len as usize) }
273 };
274 let mut result = Vec::with_capacity(profiles.len());
275
276 for profile in profiles {
277 result.push(ChromeProfileInfo {
278 profile_id: c_string_to_string(profile.profile_id),
279 profile_path: c_string_to_string(profile.profile_path),
280 display_name: c_string_to_string(profile.display_name),
281 is_default: profile.is_default,
282 });
283 }
284
285 cleanup_bridge_call("free profile list", |bridge| {
286 unsafe { bridge.cbf_bridge_profile_list_free(&mut list) };
287 });
288
289 Ok(result)
290 }
291
292 pub fn list_extensions(
294 &mut self,
295 profile_id: &str,
296 ) -> Result<Vec<ChromeExtensionInfo>, BridgeError> {
297 self.ensure_ready()?;
298
299 let profile = CString::new(profile_id).map_err(|_| BridgeError::InvalidInput)?;
300
301 let mut list = CbfExtensionInfoList::default();
302 if !bridge_call!(cbf_bridge_client_list_extensions(
303 self.inner,
304 profile.as_ptr(),
305 &mut list
306 )) {
307 return Err(BridgeError::OperationFailed {
308 operation: "list_extensions",
309 });
310 }
311
312 let result = parse_extension_list(list);
313
314 cleanup_bridge_call("free extension list", |bridge| {
315 unsafe { bridge.cbf_bridge_extension_list_free(&mut list) };
316 });
317 Ok(result)
318 }
319
320 pub fn register_custom_scheme_handler(
321 &mut self,
322 scheme: &str,
323 host: &str,
324 ) -> Result<(), BridgeError> {
325 self.ensure_ready()?;
326
327 let scheme = CString::new(scheme).map_err(|_| BridgeError::InvalidInput)?;
328 let host = CString::new(host).map_err(|_| BridgeError::InvalidInput)?;
329 bridge_ok(
330 "register_custom_scheme_handler",
331 bridge_call!(cbf_bridge_client_register_custom_scheme_handler(
332 self.inner,
333 scheme.as_ptr(),
334 host.as_ptr(),
335 )),
336 )
337 }
338
339 pub fn respond_custom_scheme_request(
340 &mut self,
341 response: &ChromeCustomSchemeResponse,
342 ) -> Result<(), BridgeError> {
343 self.ensure_ready()?;
344
345 let mime_type =
346 CString::new(response.mime_type.as_str()).map_err(|_| BridgeError::InvalidInput)?;
347 let content_security_policy = to_optional_cstring(&response.content_security_policy)
348 .map_err(|_| BridgeError::InvalidInput)?;
349 let access_control_allow_origin =
350 to_optional_cstring(&response.access_control_allow_origin)
351 .map_err(|_| BridgeError::InvalidInput)?;
352
353 let result = match response.result {
354 ChromeCustomSchemeResponseResult::Ok => {
355 CbfCustomSchemeResponseResult_kCbfCustomSchemeResponseResultOk
356 }
357 ChromeCustomSchemeResponseResult::NotFound => {
358 CbfCustomSchemeResponseResult_kCbfCustomSchemeResponseResultNotFound
359 }
360 ChromeCustomSchemeResponseResult::Aborted => {
361 CbfCustomSchemeResponseResult_kCbfCustomSchemeResponseResultAborted
362 }
363 } as u8;
364
365 let body_ptr = if response.body.is_empty() {
366 ptr::null()
367 } else {
368 response.body.as_ptr()
369 };
370
371 bridge_ok(
372 "respond_custom_scheme_request",
373 bridge_call!(cbf_bridge_client_respond_custom_scheme_request(
374 self.inner,
375 response.request_id,
376 result,
377 mime_type.as_ptr(),
378 content_security_policy
379 .as_ref()
380 .map_or(ptr::null(), |value| value.as_ptr()),
381 access_control_allow_origin
382 .as_ref()
383 .map_or(ptr::null(), |value| value.as_ptr()),
384 body_ptr,
385 response.body.len() as u32,
386 )),
387 )
388 }
389
390 pub fn activate_extension_action(
391 &mut self,
392 browsing_context_id: TabId,
393 extension_id: &str,
394 ) -> Result<(), BridgeError> {
395 self.ensure_ready()?;
396
397 let extension_id = CString::new(extension_id).map_err(|_| BridgeError::InvalidInput)?;
398 bridge_ok(
399 "activate_extension_action",
400 bridge_call!(cbf_bridge_client_activate_extension_action(
401 self.inner,
402 browsing_context_id.get(),
403 extension_id.as_ptr(),
404 )),
405 )
406 }
407
408 pub fn create_tab(
410 &mut self,
411 request_id: u64,
412 initial_url: &str,
413 profile_id: &str,
414 policy: Option<&ChromeBrowsingContextPolicy>,
415 ) -> Result<(), BridgeError> {
416 self.ensure_ready()?;
417
418 let url = CString::new(initial_url).map_err(|_| BridgeError::InvalidInput)?;
419 let profile = CString::new(profile_id).map_err(|_| BridgeError::InvalidInput)?;
420 let allowed_origins = match policy.map(|policy| &policy.ipc) {
421 Some(ChromeIpcPolicy::Allow { allowed_origins }) => Some(
422 allowed_origins
423 .iter()
424 .map(|origin| CString::new(origin.as_str()))
425 .collect::<Result<Vec<_>, _>>()
426 .map_err(|_| BridgeError::InvalidInput)?,
427 ),
428 _ => None,
429 };
430
431 let allowed_origin_ptrs: Vec<*const c_char> = allowed_origins
432 .as_ref()
433 .map(|origins| origins.iter().map(|origin| origin.as_ptr()).collect())
434 .unwrap_or_default();
435 let allowed_origin_list = CbfCommandList {
436 items: if allowed_origin_ptrs.is_empty() {
437 ptr::null_mut()
438 } else {
439 allowed_origin_ptrs.as_ptr() as *mut *const c_char
440 },
441 len: allowed_origin_ptrs.len() as u32,
442 };
443
444 let (has_policy, ipc_policy_kind, extensions_policy) = match policy {
445 Some(policy) => (
446 true,
447 match policy.ipc {
448 ChromeIpcPolicy::Deny => {
449 CbfBrowsingContextIpcPolicy_kCbfBrowsingContextIpcPolicyDeny as u8
450 }
451 ChromeIpcPolicy::Allow { .. } => {
452 CbfBrowsingContextIpcPolicy_kCbfBrowsingContextIpcPolicyAllow as u8
453 }
454 },
455 match policy.extensions {
456 ChromeCapabilityPolicy::Allow => {
457 CbfCapabilityPolicy_kCbfCapabilityPolicyAllow as u8
458 }
459 ChromeCapabilityPolicy::Deny => {
460 CbfCapabilityPolicy_kCbfCapabilityPolicyDeny as u8
461 }
462 },
463 ),
464 None => (
465 false,
466 CbfBrowsingContextIpcPolicy_kCbfBrowsingContextIpcPolicyDeny as u8,
467 CbfCapabilityPolicy_kCbfCapabilityPolicyAllow as u8,
468 ),
469 };
470
471 bridge_ok(
472 "create_tab",
473 bridge_call!(cbf_bridge_client_create_tab(
474 self.inner,
475 request_id,
476 url.as_ptr(),
477 profile.as_ptr(),
478 has_policy,
479 ipc_policy_kind,
480 &allowed_origin_list,
481 extensions_policy
482 )),
483 )
484 }
485
486 pub fn request_close_tab(&mut self, browsing_context_id: TabId) -> Result<(), BridgeError> {
488 self.ensure_ready()?;
489
490 bridge_ok(
491 "request_close_tab",
492 bridge_call!(cbf_bridge_client_request_close_tab(
493 self.inner,
494 browsing_context_id.get()
495 )),
496 )
497 }
498
499 pub fn begin_close_tabs_transaction(
501 &mut self,
502 request_id: u64,
503 browsing_context_ids: &[TabId],
504 ) -> Result<(), BridgeError> {
505 self.ensure_ready()?;
506
507 let tab_ids: Vec<u64> = browsing_context_ids.iter().map(|id| id.get()).collect();
508 let list = CbfUint64List {
509 items: if tab_ids.is_empty() {
510 ptr::null()
511 } else {
512 tab_ids.as_ptr()
513 },
514 len: tab_ids.len() as u32,
515 };
516
517 bridge_ok(
518 "begin_close_tabs_transaction",
519 bridge_call!(cbf_bridge_client_begin_close_tabs_transaction(
520 self.inner, request_id, &list
521 )),
522 )
523 }
524
525 pub fn commit_close_tabs_transaction(&mut self, request_id: u64) -> Result<(), BridgeError> {
527 self.ensure_ready()?;
528
529 bridge_ok(
530 "commit_close_tabs_transaction",
531 bridge_call!(cbf_bridge_client_commit_close_tabs_transaction(
532 self.inner, request_id
533 )),
534 )
535 }
536
537 pub fn cancel_close_tabs_transaction(&mut self, request_id: u64) -> Result<(), BridgeError> {
539 self.ensure_ready()?;
540
541 bridge_ok(
542 "cancel_close_tabs_transaction",
543 bridge_call!(cbf_bridge_client_cancel_close_tabs_transaction(
544 self.inner, request_id
545 )),
546 )
547 }
548
549 pub fn set_tab_size(
551 &mut self,
552 browsing_context_id: TabId,
553 width: u32,
554 height: u32,
555 ) -> Result<(), BridgeError> {
556 self.ensure_ready()?;
557
558 bridge_ok(
559 "set_tab_size",
560 bridge_call!(cbf_bridge_client_set_tab_size(
561 self.inner,
562 browsing_context_id.get(),
563 width,
564 height
565 )),
566 )
567 }
568
569 pub fn set_tab_focus(
571 &mut self,
572 browsing_context_id: TabId,
573 focused: bool,
574 ) -> Result<(), BridgeError> {
575 self.ensure_ready()?;
576
577 bridge_ok(
578 "set_tab_focus",
579 bridge_call!(cbf_bridge_client_set_tab_focus(
580 self.inner,
581 browsing_context_id.get(),
582 focused
583 )),
584 )
585 }
586
587 pub fn set_tab_visibility(
589 &mut self,
590 browsing_context_id: TabId,
591 visibility: ChromeTabVisibility,
592 ) -> Result<(), BridgeError> {
593 self.ensure_ready()?;
594
595 let visibility = match visibility {
596 ChromeTabVisibility::Visible => CbfTabVisibility_kCbfTabVisibilityVisible,
597 ChromeTabVisibility::Hidden => CbfTabVisibility_kCbfTabVisibilityHidden,
598 } as u8;
599
600 bridge_ok(
601 "set_tab_visibility",
602 bridge_call!(cbf_bridge_client_set_tab_visibility(
603 self.inner,
604 browsing_context_id.get(),
605 visibility,
606 )),
607 )
608 }
609
610 pub fn set_tab_execution_state(
612 &mut self,
613 browsing_context_id: TabId,
614 state: ChromeTabExecutionState,
615 ) -> Result<(), BridgeError> {
616 self.ensure_ready()?;
617
618 bridge_ok(
619 "set_tab_execution_state",
620 bridge_call!(cbf_bridge_client_set_tab_execution_state(
621 self.inner,
622 browsing_context_id.get(),
623 execution_state_to_ffi(state),
624 )),
625 )
626 }
627
628 pub fn enable_tab_ipc(
630 &mut self,
631 browsing_context_id: TabId,
632 config: &TabIpcConfig,
633 ) -> Result<(), BridgeError> {
634 self.ensure_ready()?;
635
636 let origin_cstrings: Result<Vec<CString>, _> = config
637 .allowed_origins
638 .iter()
639 .map(|origin| CString::new(origin.as_str()))
640 .collect();
641 let origin_cstrings = origin_cstrings.map_err(|_| BridgeError::InvalidInput)?;
642 let origin_ptrs: Vec<*const c_char> = origin_cstrings.iter().map(|s| s.as_ptr()).collect();
643
644 let list = CbfCommandList {
645 items: if origin_ptrs.is_empty() {
646 ptr::null_mut()
647 } else {
648 origin_ptrs.as_ptr() as *mut *const c_char
649 },
650 len: origin_ptrs.len() as u32,
651 };
652
653 bridge_ok(
654 "enable_tab_ipc",
655 bridge_call!(cbf_bridge_client_enable_tab_ipc(
656 self.inner,
657 browsing_context_id.get(),
658 &list
659 )),
660 )
661 }
662
663 pub fn disable_tab_ipc(&mut self, browsing_context_id: TabId) -> Result<(), BridgeError> {
665 self.ensure_ready()?;
666
667 bridge_ok(
668 "disable_tab_ipc",
669 bridge_call!(cbf_bridge_client_disable_tab_ipc(
670 self.inner,
671 browsing_context_id.get()
672 )),
673 )
674 }
675
676 pub fn post_tab_ipc_message(
678 &mut self,
679 browsing_context_id: TabId,
680 message: &TabIpcMessage,
681 ) -> Result<(), BridgeError> {
682 self.ensure_ready()?;
683
684 let channel =
685 CString::new(message.channel.as_str()).map_err(|_| BridgeError::InvalidInput)?;
686 let content_type =
687 to_optional_cstring(&message.content_type).map_err(|_| BridgeError::InvalidInput)?;
688 let payload_kind = match message.payload {
689 TabIpcPayload::Text(_) => CbfIpcPayloadKind_kCbfIpcPayloadText,
690 TabIpcPayload::Binary(_) => CbfIpcPayloadKind_kCbfIpcPayloadBinary,
691 } as u8;
692 let message_type = ipc_message_type_to_ffi(message.message_type);
693 let error_code = message
694 .error_code
695 .map(ipc_error_code_to_ffi)
696 .unwrap_or(CbfIpcErrorCode_kCbfIpcErrorNone as u8);
697 let (payload_text, payload_binary) = match &message.payload {
698 TabIpcPayload::Text(text) => (
699 Some(CString::new(text.as_str()).map_err(|_| BridgeError::InvalidInput)?),
700 Vec::new(),
701 ),
702 TabIpcPayload::Binary(binary) => (None, binary.clone()),
703 };
704
705 bridge_ok(
706 "post_tab_ipc_message",
707 bridge_call!(cbf_bridge_client_post_tab_ipc_message(
708 self.inner,
709 browsing_context_id.get(),
710 channel.as_ptr(),
711 message_type,
712 message.request_id,
713 payload_kind,
714 payload_text
715 .as_ref()
716 .map(|value| value.as_ptr())
717 .unwrap_or(ptr::null()),
718 if payload_binary.is_empty() {
719 ptr::null()
720 } else {
721 payload_binary.as_ptr()
722 },
723 payload_binary.len() as u32,
724 content_type
725 .as_ref()
726 .map(|value| value.as_ptr())
727 .unwrap_or(ptr::null()),
728 error_code,
729 )),
730 )
731 }
732
733 pub fn set_tab_background_policy(
735 &mut self,
736 browsing_context_id: TabId,
737 policy: ChromeBackgroundPolicy,
738 ) -> Result<(), BridgeError> {
739 self.ensure_ready()?;
740
741 let transparent = matches!(policy, ChromeBackgroundPolicy::Transparent);
742
743 bridge_ok(
744 "set_tab_background_policy",
745 bridge_call!(cbf_bridge_client_set_tab_background_policy(
746 self.inner,
747 browsing_context_id.get(),
748 transparent,
749 )),
750 )
751 }
752
753 pub fn confirm_beforeunload(
755 &mut self,
756 browsing_context_id: TabId,
757 request_id: u64,
758 proceed: bool,
759 ) -> Result<(), BridgeError> {
760 self.ensure_ready()?;
761
762 bridge_ok(
763 "confirm_beforeunload",
764 bridge_call!(cbf_bridge_client_confirm_beforeunload(
765 self.inner,
766 browsing_context_id.get(),
767 request_id,
768 proceed,
769 )),
770 )
771 }
772
773 pub fn respond_javascript_dialog(
775 &mut self,
776 browsing_context_id: TabId,
777 request_id: u64,
778 accept: bool,
779 prompt_text: Option<&str>,
780 ) -> Result<(), BridgeError> {
781 self.ensure_ready()?;
782
783 let prompt_text = to_optional_cstring(&prompt_text.map(ToOwned::to_owned))
784 .map_err(|_| BridgeError::InvalidInput)?;
785
786 bridge_ok(
787 "respond_javascript_dialog",
788 bridge_call!(cbf_bridge_client_respond_javascript_dialog(
789 self.inner,
790 browsing_context_id.get(),
791 request_id,
792 accept,
793 prompt_text.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
794 )),
795 )
796 }
797
798 pub fn respond_extension_popup_javascript_dialog(
800 &mut self,
801 popup_id: PopupId,
802 request_id: u64,
803 accept: bool,
804 prompt_text: Option<&str>,
805 ) -> Result<(), BridgeError> {
806 self.ensure_ready()?;
807
808 let prompt_text = to_optional_cstring(&prompt_text.map(ToOwned::to_owned))
809 .map_err(|_| BridgeError::InvalidInput)?;
810
811 bridge_ok(
812 "respond_extension_popup_javascript_dialog",
813 bridge_call!(cbf_bridge_client_respond_extension_popup_javascript_dialog(
814 self.inner,
815 popup_id.get(),
816 request_id,
817 accept,
818 prompt_text.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
819 )),
820 )
821 }
822
823 pub fn navigate(&mut self, browsing_context_id: TabId, url: &str) -> Result<(), BridgeError> {
825 self.ensure_ready()?;
826
827 let url = CString::new(url).map_err(|_| BridgeError::InvalidInput)?;
828
829 bridge_ok(
830 "navigate",
831 bridge_call!(cbf_bridge_client_navigate(
832 self.inner,
833 browsing_context_id.get(),
834 url.as_ptr()
835 )),
836 )
837 }
838
839 pub fn go_back(&mut self, browsing_context_id: TabId) -> Result<(), BridgeError> {
841 self.ensure_ready()?;
842
843 bridge_ok(
844 "go_back",
845 bridge_call!(cbf_bridge_client_go_back(
846 self.inner,
847 browsing_context_id.get()
848 )),
849 )
850 }
851
852 pub fn go_forward(&mut self, browsing_context_id: TabId) -> Result<(), BridgeError> {
854 self.ensure_ready()?;
855
856 bridge_ok(
857 "go_forward",
858 bridge_call!(cbf_bridge_client_go_forward(
859 self.inner,
860 browsing_context_id.get()
861 )),
862 )
863 }
864
865 pub fn reload(
867 &mut self,
868 browsing_context_id: TabId,
869 ignore_cache: bool,
870 ) -> Result<(), BridgeError> {
871 self.ensure_ready()?;
872
873 bridge_ok(
874 "reload",
875 bridge_call!(cbf_bridge_client_reload(
876 self.inner,
877 browsing_context_id.get(),
878 ignore_cache
879 )),
880 )
881 }
882
883 pub fn print_preview(&mut self, browsing_context_id: TabId) -> Result<(), BridgeError> {
885 self.ensure_ready()?;
886
887 bridge_ok(
888 "print_preview",
889 bridge_call!(cbf_bridge_client_print_preview(
890 self.inner,
891 browsing_context_id.get()
892 )),
893 )
894 }
895
896 pub fn open_dev_tools(&mut self, browsing_context_id: TabId) -> Result<(), BridgeError> {
898 self.ensure_ready()?;
899
900 bridge_ok(
901 "open_dev_tools",
902 bridge_call!(cbf_bridge_client_open_dev_tools(
903 self.inner,
904 browsing_context_id.get()
905 )),
906 )
907 }
908
909 pub fn inspect_element(
911 &mut self,
912 browsing_context_id: TabId,
913 x: i32,
914 y: i32,
915 ) -> Result<(), BridgeError> {
916 self.ensure_ready()?;
917
918 bridge_ok(
919 "inspect_element",
920 bridge_call!(cbf_bridge_client_inspect_element(
921 self.inner,
922 browsing_context_id.get(),
923 x,
924 y
925 )),
926 )
927 }
928
929 pub fn get_tab_dom_html(
931 &mut self,
932 browsing_context_id: TabId,
933 request_id: u64,
934 ) -> Result<(), BridgeError> {
935 self.ensure_ready()?;
936
937 bridge_ok(
938 "get_tab_dom_html",
939 bridge_call!(cbf_bridge_client_get_tab_dom_html(
940 self.inner,
941 browsing_context_id.get(),
942 request_id,
943 )),
944 )
945 }
946
947 pub fn get_navigation_history(
949 &mut self,
950 browsing_context_id: TabId,
951 request_id: u64,
952 ) -> Result<(), BridgeError> {
953 self.ensure_ready()?;
954
955 bridge_ok(
956 "get_navigation_history",
957 bridge_call!(cbf_bridge_client_get_navigation_history(
958 self.inner,
959 browsing_context_id.get(),
960 request_id,
961 )),
962 )
963 }
964
965 pub fn traverse_history_to_entry(
967 &mut self,
968 browsing_context_id: TabId,
969 entry_id: ChromeNavigationEntryId,
970 ) -> Result<(), BridgeError> {
971 self.ensure_ready()?;
972
973 let success = bridge_call!(cbf_bridge_client_traverse_history_to_entry(
974 self.inner,
975 browsing_context_id.get(),
976 entry_id.get(),
977 ));
978
979 if success {
980 Ok(())
981 } else {
982 Err(BridgeError::InvalidInput)
983 }
984 }
985
986 pub fn traverse_history_by_offset(
988 &mut self,
989 browsing_context_id: TabId,
990 delta: i32,
991 ) -> Result<(), BridgeError> {
992 self.ensure_ready()?;
993
994 bridge_ok(
995 "traverse_history_by_offset",
996 bridge_call!(cbf_bridge_client_traverse_history_by_offset(
997 self.inner,
998 browsing_context_id.get(),
999 delta,
1000 )),
1001 )
1002 }
1003
1004 pub fn find_in_page(
1005 &mut self,
1006 browsing_context_id: TabId,
1007 request_id: u64,
1008 options: &ChromeFindInPageOptions,
1009 ) -> Result<(), BridgeError> {
1010 self.ensure_ready()?;
1011
1012 let query = CString::new(options.query.as_str()).map_err(|_| BridgeError::InvalidInput)?;
1013 bridge_ok(
1014 "find_in_page",
1015 bridge_call!(cbf_bridge_client_find_in_page(
1016 self.inner,
1017 browsing_context_id.get(),
1018 request_id,
1019 query.as_ptr(),
1020 options.forward,
1021 options.match_case,
1022 options.new_session,
1023 options.find_match,
1024 )),
1025 )
1026 }
1027
1028 pub fn stop_finding(
1029 &mut self,
1030 browsing_context_id: TabId,
1031 action: ChromeStopFindAction,
1032 ) -> Result<(), BridgeError> {
1033 self.ensure_ready()?;
1034
1035 bridge_ok(
1036 "stop_finding",
1037 bridge_call!(cbf_bridge_client_stop_finding(
1038 self.inner,
1039 browsing_context_id.get(),
1040 action.to_ffi(),
1041 )),
1042 )
1043 }
1044
1045 pub fn open_default_prompt_ui(
1047 &mut self,
1048 profile_id: &str,
1049 request_id: u64,
1050 ) -> Result<(), BridgeError> {
1051 self.ensure_ready()?;
1052 let profile_id = CString::new(profile_id).map_err(|_| BridgeError::InvalidInput)?;
1053 bridge_ok(
1054 "open_default_prompt_ui",
1055 bridge_call!(cbf_bridge_client_open_default_prompt_ui(
1056 self.inner,
1057 profile_id.as_ptr(),
1058 request_id,
1059 )),
1060 )
1061 }
1062
1063 pub fn respond_prompt_ui(
1065 &mut self,
1066 profile_id: &str,
1067 request_id: u64,
1068 response: &PromptUiResponse,
1069 ) -> Result<(), BridgeError> {
1070 self.ensure_ready()?;
1071 let profile_id = CString::new(profile_id).map_err(|_| BridgeError::InvalidInput)?;
1072 let (prompt_ui_kind, proceed, destination_path, report_abuse) = match response {
1073 PromptUiResponse::PermissionPrompt { allow } => (
1074 CbfPromptUiKind_kCbfPromptUiKindPermissionPrompt,
1075 *allow,
1076 None,
1077 false,
1078 ),
1079 PromptUiResponse::DownloadPrompt {
1080 allow,
1081 destination_path,
1082 } => (
1083 CbfPromptUiKind_kCbfPromptUiKindDownloadPrompt,
1084 *allow,
1085 to_optional_cstring(destination_path)?,
1086 false,
1087 ),
1088 PromptUiResponse::ExtensionInstallPrompt { proceed } => (
1089 CbfPromptUiKind_kCbfPromptUiKindExtensionInstallPrompt,
1090 *proceed,
1091 None,
1092 false,
1093 ),
1094 PromptUiResponse::ExtensionUninstallPrompt {
1095 proceed,
1096 report_abuse,
1097 } => (
1098 CbfPromptUiKind_kCbfPromptUiKindExtensionUninstallPrompt,
1099 *proceed,
1100 None,
1101 *report_abuse,
1102 ),
1103 PromptUiResponse::PrintPreviewDialog { proceed } => (
1104 CbfPromptUiKind_kCbfPromptUiKindPrintPreviewDialog,
1105 *proceed,
1106 None,
1107 false,
1108 ),
1109 PromptUiResponse::FormResubmissionPrompt { proceed } => (
1110 CbfPromptUiKind_kCbfPromptUiKindFormResubmissionPrompt,
1111 *proceed,
1112 None,
1113 false,
1114 ),
1115 PromptUiResponse::Unknown => {
1116 (CbfPromptUiKind_kCbfPromptUiKindUnknown, false, None, false)
1117 }
1118 };
1119 bridge_ok(
1120 "respond_prompt_ui",
1121 bridge_call!(cbf_bridge_client_respond_prompt_ui(
1122 self.inner,
1123 profile_id.as_ptr(),
1124 request_id,
1125 prompt_ui_kind as u8,
1126 proceed,
1127 report_abuse,
1128 destination_path
1129 .as_ref()
1130 .map_or(ptr::null(), |path| path.as_ptr()),
1131 )),
1132 )
1133 }
1134
1135 pub fn respond_prompt_ui_for_tab(
1137 &mut self,
1138 browsing_context_id: TabId,
1139 request_id: u64,
1140 response: &PromptUiResponse,
1141 ) -> Result<(), BridgeError> {
1142 self.ensure_ready()?;
1143 let (prompt_ui_kind, proceed, destination_path, report_abuse) = match response {
1144 PromptUiResponse::PermissionPrompt { allow } => (
1145 CbfPromptUiKind_kCbfPromptUiKindPermissionPrompt,
1146 *allow,
1147 None,
1148 false,
1149 ),
1150 PromptUiResponse::DownloadPrompt {
1151 allow,
1152 destination_path,
1153 } => (
1154 CbfPromptUiKind_kCbfPromptUiKindDownloadPrompt,
1155 *allow,
1156 to_optional_cstring(destination_path)?,
1157 false,
1158 ),
1159 PromptUiResponse::ExtensionInstallPrompt { proceed } => (
1160 CbfPromptUiKind_kCbfPromptUiKindExtensionInstallPrompt,
1161 *proceed,
1162 None,
1163 false,
1164 ),
1165 PromptUiResponse::ExtensionUninstallPrompt {
1166 proceed,
1167 report_abuse,
1168 } => (
1169 CbfPromptUiKind_kCbfPromptUiKindExtensionUninstallPrompt,
1170 *proceed,
1171 None,
1172 *report_abuse,
1173 ),
1174 PromptUiResponse::PrintPreviewDialog { proceed } => (
1175 CbfPromptUiKind_kCbfPromptUiKindPrintPreviewDialog,
1176 *proceed,
1177 None,
1178 false,
1179 ),
1180 PromptUiResponse::FormResubmissionPrompt { proceed } => (
1181 CbfPromptUiKind_kCbfPromptUiKindFormResubmissionPrompt,
1182 *proceed,
1183 None,
1184 false,
1185 ),
1186 PromptUiResponse::Unknown => {
1187 (CbfPromptUiKind_kCbfPromptUiKindUnknown, false, None, false)
1188 }
1189 };
1190 bridge_ok(
1191 "respond_prompt_ui_for_tab",
1192 bridge_call!(cbf_bridge_client_respond_prompt_ui_for_tab(
1193 self.inner,
1194 browsing_context_id.get(),
1195 request_id,
1196 prompt_ui_kind as u8,
1197 proceed,
1198 report_abuse,
1199 destination_path
1200 .as_ref()
1201 .map_or(ptr::null(), |path| path.as_ptr()),
1202 )),
1203 )
1204 }
1205
1206 pub fn close_prompt_ui(
1208 &mut self,
1209 profile_id: &str,
1210 prompt_ui_id: PromptUiId,
1211 ) -> Result<(), BridgeError> {
1212 self.ensure_ready()?;
1213 let profile_id = CString::new(profile_id).map_err(|_| BridgeError::InvalidInput)?;
1214 bridge_ok(
1215 "close_prompt_ui",
1216 bridge_call!(cbf_bridge_client_close_prompt_ui(
1217 self.inner,
1218 profile_id.as_ptr(),
1219 prompt_ui_id.get(),
1220 )),
1221 )
1222 }
1223
1224 pub fn pause_download(&mut self, download_id: ChromeDownloadId) -> Result<(), BridgeError> {
1226 self.ensure_ready()?;
1227 bridge_ok(
1228 "pause_download",
1229 bridge_call!(cbf_bridge_client_pause_download(
1230 self.inner,
1231 download_id.get()
1232 )),
1233 )
1234 }
1235
1236 pub fn resume_download(&mut self, download_id: ChromeDownloadId) -> Result<(), BridgeError> {
1238 self.ensure_ready()?;
1239 bridge_ok(
1240 "resume_download",
1241 bridge_call!(cbf_bridge_client_resume_download(
1242 self.inner,
1243 download_id.get()
1244 )),
1245 )
1246 }
1247
1248 pub fn cancel_download(&mut self, download_id: ChromeDownloadId) -> Result<(), BridgeError> {
1250 self.ensure_ready()?;
1251 bridge_ok(
1252 "cancel_download",
1253 bridge_call!(cbf_bridge_client_cancel_download(
1254 self.inner,
1255 download_id.get()
1256 )),
1257 )
1258 }
1259
1260 pub fn respond_tab_open(
1262 &mut self,
1263 request_id: u64,
1264 response: &ChromeBrowsingContextOpenResponse,
1265 ) -> Result<(), BridgeError> {
1266 self.ensure_ready()?;
1267 let (response_kind, target_tab_id, activate) = match response {
1268 ChromeBrowsingContextOpenResponse::AllowNewContext { activate } => (
1269 CbfTabOpenResponseKind_kCbfTabOpenResponseAllowNewContext,
1270 0,
1271 *activate,
1272 ),
1273 ChromeBrowsingContextOpenResponse::AllowExistingContext { tab_id, activate } => (
1274 CbfTabOpenResponseKind_kCbfTabOpenResponseAllowExistingContext,
1275 tab_id.get(),
1276 *activate,
1277 ),
1278 ChromeBrowsingContextOpenResponse::Deny => {
1279 (CbfTabOpenResponseKind_kCbfTabOpenResponseDeny, 0, false)
1280 }
1281 };
1282 bridge_ok(
1283 "respond_tab_open",
1284 bridge_call!(cbf_bridge_client_respond_tab_open(
1285 self.inner,
1286 request_id,
1287 response_kind as u8,
1288 target_tab_id,
1289 activate,
1290 )),
1291 )
1292 }
1293
1294 pub fn respond_window_open(
1298 &mut self,
1299 request_id: u64,
1300 response: &WindowOpenResponse,
1301 ) -> Result<(), BridgeError> {
1302 let tab_open_response = match response {
1303 WindowOpenResponse::AllowExistingWindow { .. }
1304 | WindowOpenResponse::AllowNewWindow { .. } => {
1305 ChromeBrowsingContextOpenResponse::AllowNewContext { activate: true }
1306 }
1307 WindowOpenResponse::Deny => ChromeBrowsingContextOpenResponse::Deny,
1308 };
1309 self.respond_tab_open(request_id, &tab_open_response)
1310 }
1311
1312 pub fn send_key_event_raw(
1314 &mut self,
1315 browsing_context_id: TabId,
1316 event: &ChromeKeyEvent,
1317 commands: &[String],
1318 ) -> Result<(), BridgeError> {
1319 self.ensure_ready()?;
1320
1321 let dom_code = to_optional_cstring(&event.dom_code)?;
1322 let dom_key = to_optional_cstring(&event.dom_key)?;
1323 let text = to_optional_cstring(&event.text)?;
1324 let unmodified_text = to_optional_cstring(&event.unmodified_text)?;
1325
1326 let command_cstrings = commands
1327 .iter()
1328 .map(|command| CString::new(command.as_str()).map_err(|_| BridgeError::InvalidInput))
1329 .collect::<Result<Vec<_>, _>>()?;
1330 let command_ptrs: Vec<*const std::os::raw::c_char> =
1331 command_cstrings.iter().map(|cstr| cstr.as_ptr()).collect();
1332
1333 let ffi_event = CbfKeyEvent {
1334 tab_id: browsing_context_id.get(),
1335 type_: key_event_type_to_ffi(event.type_),
1336 modifiers: event.modifiers,
1337 windows_key_code: event.windows_key_code,
1338 native_key_code: event.native_key_code,
1339 dom_code: dom_code.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
1340 dom_key: dom_key.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
1341 text: text.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
1342 unmodified_text: unmodified_text.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
1343 auto_repeat: event.auto_repeat,
1344 is_keypad: event.is_keypad,
1345 is_system_key: event.is_system_key,
1346 location: event.location,
1347 };
1348
1349 let ffi_commands = CbfCommandList {
1350 items: if command_ptrs.is_empty() {
1351 ptr::null_mut()
1352 } else {
1353 command_ptrs.as_ptr() as *mut *const c_char
1354 },
1355 len: command_ptrs.len() as u32,
1356 };
1357
1358 bridge_ok(
1359 "send_key_event",
1360 bridge_call!(cbf_bridge_client_send_key_event(
1361 self.inner,
1362 &ffi_event,
1363 &ffi_commands
1364 )),
1365 )
1366 }
1367
1368 pub fn send_extension_popup_key_event_raw(
1370 &mut self,
1371 popup_id: PopupId,
1372 event: &ChromeKeyEvent,
1373 commands: &[String],
1374 ) -> Result<(), BridgeError> {
1375 self.ensure_ready()?;
1376
1377 let dom_code = to_optional_cstring(&event.dom_code)?;
1378 let dom_key = to_optional_cstring(&event.dom_key)?;
1379 let text = to_optional_cstring(&event.text)?;
1380 let unmodified_text = to_optional_cstring(&event.unmodified_text)?;
1381
1382 let command_cstrings = commands
1383 .iter()
1384 .map(|command| CString::new(command.as_str()).map_err(|_| BridgeError::InvalidInput))
1385 .collect::<Result<Vec<_>, _>>()?;
1386 let command_ptrs: Vec<*const std::os::raw::c_char> =
1387 command_cstrings.iter().map(|cstr| cstr.as_ptr()).collect();
1388
1389 let ffi_event = CbfKeyEvent {
1390 tab_id: 0,
1391 type_: key_event_type_to_ffi(event.type_),
1392 modifiers: event.modifiers,
1393 windows_key_code: event.windows_key_code,
1394 native_key_code: event.native_key_code,
1395 dom_code: dom_code.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
1396 dom_key: dom_key.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
1397 text: text.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
1398 unmodified_text: unmodified_text.as_ref().map_or(ptr::null(), |v| v.as_ptr()),
1399 auto_repeat: event.auto_repeat,
1400 is_keypad: event.is_keypad,
1401 is_system_key: event.is_system_key,
1402 location: event.location,
1403 };
1404
1405 let ffi_commands = CbfCommandList {
1406 items: if command_ptrs.is_empty() {
1407 ptr::null_mut()
1408 } else {
1409 command_ptrs.as_ptr() as *mut *const c_char
1410 },
1411 len: command_ptrs.len() as u32,
1412 };
1413
1414 bridge_ok(
1415 "send_extension_popup_key_event_raw",
1416 bridge_call!(cbf_bridge_client_send_extension_popup_key_event(
1417 self.inner,
1418 popup_id.get(),
1419 &ffi_event,
1420 &ffi_commands,
1421 )),
1422 )
1423 }
1424
1425 pub fn send_mouse_event(
1427 &mut self,
1428 browsing_context_id: TabId,
1429 event: &ChromeMouseEvent,
1430 ) -> Result<(), BridgeError> {
1431 self.ensure_ready()?;
1432
1433 let ffi_event = CbfMouseEvent {
1434 tab_id: browsing_context_id.get(),
1435 type_: mouse_event_type_to_ffi(event.type_),
1436 modifiers: event.modifiers,
1437 button: mouse_button_to_ffi(event.button),
1438 click_count: event.click_count,
1439 position_in_widget_x: event.position_in_widget_x,
1440 position_in_widget_y: event.position_in_widget_y,
1441 position_in_screen_x: event.position_in_screen_x,
1442 position_in_screen_y: event.position_in_screen_y,
1443 movement_x: event.movement_x,
1444 movement_y: event.movement_y,
1445 is_raw_movement_event: event.is_raw_movement_event,
1446 pointer_type: pointer_type_to_ffi(event.pointer_type),
1447 };
1448
1449 bridge_ok(
1450 "send_mouse_event",
1451 bridge_call!(cbf_bridge_client_send_mouse_event(self.inner, &ffi_event)),
1452 )
1453 }
1454
1455 pub fn send_extension_popup_mouse_event(
1457 &mut self,
1458 popup_id: PopupId,
1459 event: &ChromeMouseEvent,
1460 ) -> Result<(), BridgeError> {
1461 self.ensure_ready()?;
1462
1463 let ffi_event = CbfMouseEvent {
1464 tab_id: 0,
1465 type_: mouse_event_type_to_ffi(event.type_),
1466 modifiers: event.modifiers,
1467 button: mouse_button_to_ffi(event.button),
1468 click_count: event.click_count,
1469 position_in_widget_x: event.position_in_widget_x,
1470 position_in_widget_y: event.position_in_widget_y,
1471 position_in_screen_x: event.position_in_screen_x,
1472 position_in_screen_y: event.position_in_screen_y,
1473 movement_x: event.movement_x,
1474 movement_y: event.movement_y,
1475 is_raw_movement_event: event.is_raw_movement_event,
1476 pointer_type: pointer_type_to_ffi(event.pointer_type),
1477 };
1478
1479 bridge_ok(
1480 "send_extension_popup_mouse_event",
1481 bridge_call!(cbf_bridge_client_send_extension_popup_mouse_event(
1482 self.inner,
1483 popup_id.get(),
1484 &ffi_event,
1485 )),
1486 )
1487 }
1488
1489 pub fn send_mouse_wheel_event_raw(
1491 &mut self,
1492 browsing_context_id: TabId,
1493 event: &ChromeMouseWheelEvent,
1494 ) -> Result<(), BridgeError> {
1495 self.ensure_ready()?;
1496
1497 let ffi_event = CbfMouseWheelEvent {
1498 tab_id: browsing_context_id.get(),
1499 modifiers: event.modifiers,
1500 position_in_widget_x: event.position_in_widget_x,
1501 position_in_widget_y: event.position_in_widget_y,
1502 position_in_screen_x: event.position_in_screen_x,
1503 position_in_screen_y: event.position_in_screen_y,
1504 movement_x: event.movement_x,
1505 movement_y: event.movement_y,
1506 is_raw_movement_event: event.is_raw_movement_event,
1507 delta_x: event.delta_x,
1508 delta_y: event.delta_y,
1509 wheel_ticks_x: event.wheel_ticks_x,
1510 wheel_ticks_y: event.wheel_ticks_y,
1511 phase: event.phase,
1512 momentum_phase: event.momentum_phase,
1513 delta_units: scroll_granularity_to_ffi(event.delta_units),
1514 };
1515
1516 bridge_ok(
1517 "send_mouse_wheel_event",
1518 bridge_call!(cbf_bridge_client_send_mouse_wheel_event(
1519 self.inner, &ffi_event
1520 )),
1521 )
1522 }
1523
1524 pub fn send_extension_popup_mouse_wheel_event_raw(
1526 &mut self,
1527 popup_id: PopupId,
1528 event: &ChromeMouseWheelEvent,
1529 ) -> Result<(), BridgeError> {
1530 self.ensure_ready()?;
1531
1532 let ffi_event = CbfMouseWheelEvent {
1533 tab_id: 0,
1534 modifiers: event.modifiers,
1535 position_in_widget_x: event.position_in_widget_x,
1536 position_in_widget_y: event.position_in_widget_y,
1537 position_in_screen_x: event.position_in_screen_x,
1538 position_in_screen_y: event.position_in_screen_y,
1539 movement_x: event.movement_x,
1540 movement_y: event.movement_y,
1541 is_raw_movement_event: event.is_raw_movement_event,
1542 delta_x: event.delta_x,
1543 delta_y: event.delta_y,
1544 wheel_ticks_x: event.wheel_ticks_x,
1545 wheel_ticks_y: event.wheel_ticks_y,
1546 phase: event.phase,
1547 momentum_phase: event.momentum_phase,
1548 delta_units: scroll_granularity_to_ffi(event.delta_units),
1549 };
1550
1551 bridge_ok(
1552 "send_extension_popup_mouse_wheel_event_raw",
1553 bridge_call!(cbf_bridge_client_send_extension_popup_mouse_wheel_event(
1554 self.inner,
1555 popup_id.get(),
1556 &ffi_event,
1557 )),
1558 )
1559 }
1560
1561 pub fn send_drag_update(&mut self, update: &ChromeDragUpdate) -> Result<(), BridgeError> {
1563 self.ensure_ready()?;
1564
1565 let ffi_update = CbfDragUpdate {
1566 session_id: update.session_id,
1567 tab_id: update.browsing_context_id.get(),
1568 allowed_operations: update.allowed_operations.bits(),
1569 modifiers: update.modifiers,
1570 position_in_widget_x: update.position_in_widget_x,
1571 position_in_widget_y: update.position_in_widget_y,
1572 position_in_screen_x: update.position_in_screen_x,
1573 position_in_screen_y: update.position_in_screen_y,
1574 };
1575
1576 bridge_ok(
1577 "send_drag_update",
1578 bridge_call!(cbf_bridge_client_send_drag_update(self.inner, &ffi_update)),
1579 )
1580 }
1581
1582 pub fn send_drag_drop(&mut self, drop: &ChromeDragDrop) -> Result<(), BridgeError> {
1584 self.ensure_ready()?;
1585
1586 let ffi_drop = CbfDragDrop {
1587 session_id: drop.session_id,
1588 tab_id: drop.browsing_context_id.get(),
1589 modifiers: drop.modifiers,
1590 position_in_widget_x: drop.position_in_widget_x,
1591 position_in_widget_y: drop.position_in_widget_y,
1592 position_in_screen_x: drop.position_in_screen_x,
1593 position_in_screen_y: drop.position_in_screen_y,
1594 };
1595
1596 bridge_ok(
1597 "send_drag_drop",
1598 bridge_call!(cbf_bridge_client_send_drag_drop(self.inner, &ffi_drop)),
1599 )
1600 }
1601
1602 pub fn send_drag_cancel(
1604 &mut self,
1605 session_id: u64,
1606 browsing_context_id: TabId,
1607 ) -> Result<(), BridgeError> {
1608 self.ensure_ready()?;
1609
1610 bridge_ok(
1611 "send_drag_cancel",
1612 bridge_call!(cbf_bridge_client_send_drag_cancel(
1613 self.inner,
1614 session_id,
1615 browsing_context_id.get(),
1616 )),
1617 )
1618 }
1619
1620 pub fn send_external_drag_enter(
1622 &mut self,
1623 event: &ChromeExternalDragEnter,
1624 ) -> Result<(), BridgeError> {
1625 self.ensure_ready()?;
1626
1627 let mut owned_data = OwnedDragData::new(&event.data)?;
1628 let ffi_event = CbfExternalDragEnter {
1629 tab_id: event.browsing_context_id.get(),
1630 data: owned_data.as_ffi(),
1631 allowed_operations: event.allowed_operations.bits(),
1632 modifiers: event.modifiers,
1633 position_in_widget_x: event.position_in_widget_x,
1634 position_in_widget_y: event.position_in_widget_y,
1635 position_in_screen_x: event.position_in_screen_x,
1636 position_in_screen_y: event.position_in_screen_y,
1637 };
1638
1639 bridge_ok(
1640 "send_external_drag_enter",
1641 bridge_call!(cbf_bridge_client_send_external_drag_enter(
1642 self.inner, &ffi_event
1643 )),
1644 )
1645 }
1646
1647 pub fn send_external_drag_update(
1649 &mut self,
1650 event: &ChromeExternalDragUpdate,
1651 ) -> Result<(), BridgeError> {
1652 self.ensure_ready()?;
1653
1654 let ffi_event = CbfExternalDragUpdate {
1655 tab_id: event.browsing_context_id.get(),
1656 allowed_operations: event.allowed_operations.bits(),
1657 modifiers: event.modifiers,
1658 position_in_widget_x: event.position_in_widget_x,
1659 position_in_widget_y: event.position_in_widget_y,
1660 position_in_screen_x: event.position_in_screen_x,
1661 position_in_screen_y: event.position_in_screen_y,
1662 };
1663
1664 bridge_ok(
1665 "send_external_drag_update",
1666 bridge_call!(cbf_bridge_client_send_external_drag_update(
1667 self.inner, &ffi_event
1668 )),
1669 )
1670 }
1671
1672 pub fn send_external_drag_leave(
1674 &mut self,
1675 browsing_context_id: TabId,
1676 ) -> Result<(), BridgeError> {
1677 self.ensure_ready()?;
1678
1679 bridge_ok(
1680 "send_external_drag_leave",
1681 bridge_call!(cbf_bridge_client_send_external_drag_leave(
1682 self.inner,
1683 browsing_context_id.get()
1684 )),
1685 )
1686 }
1687
1688 pub fn send_external_drag_drop(
1690 &mut self,
1691 event: &ChromeExternalDragDrop,
1692 ) -> Result<(), BridgeError> {
1693 self.ensure_ready()?;
1694
1695 let ffi_event = CbfExternalDragDrop {
1696 tab_id: event.browsing_context_id.get(),
1697 modifiers: event.modifiers,
1698 position_in_widget_x: event.position_in_widget_x,
1699 position_in_widget_y: event.position_in_widget_y,
1700 position_in_screen_x: event.position_in_screen_x,
1701 position_in_screen_y: event.position_in_screen_y,
1702 };
1703
1704 bridge_ok(
1705 "send_external_drag_drop",
1706 bridge_call!(cbf_bridge_client_send_external_drag_drop(
1707 self.inner, &ffi_event
1708 )),
1709 )
1710 }
1711
1712 pub fn set_composition(
1714 &mut self,
1715 composition: &ChromeImeComposition,
1716 ) -> Result<(), BridgeError> {
1717 self.ensure_ready()?;
1718
1719 let text =
1720 CString::new(composition.text.as_str()).map_err(|_| BridgeError::InvalidInput)?;
1721 let spans = to_ffi_ime_text_spans(&composition.spans);
1722 let span_list = CbfImeTextSpanList {
1723 items: if spans.is_empty() {
1724 ptr::null()
1725 } else {
1726 spans.as_ptr()
1727 },
1728 len: spans.len() as u32,
1729 };
1730 let (replacement_start, replacement_end) = ime_range_to_ffi(&composition.replacement_range);
1731
1732 let ffi_composition = CbfImeComposition {
1733 tab_id: composition.browsing_context_id.get(),
1734 text: text.as_ptr(),
1735 selection_start: composition.selection_start,
1736 selection_end: composition.selection_end,
1737 replacement_range_start: replacement_start,
1738 replacement_range_end: replacement_end,
1739 spans: span_list,
1740 };
1741
1742 bridge_ok(
1743 "set_composition",
1744 bridge_call!(cbf_bridge_client_set_composition(
1745 self.inner,
1746 &ffi_composition
1747 )),
1748 )
1749 }
1750
1751 pub fn set_extension_popup_composition(
1753 &mut self,
1754 composition: &ChromeTransientImeComposition,
1755 ) -> Result<(), BridgeError> {
1756 self.ensure_ready()?;
1757
1758 let text =
1759 CString::new(composition.text.as_str()).map_err(|_| BridgeError::InvalidInput)?;
1760 let spans = to_ffi_ime_text_spans(&composition.spans);
1761 let span_list = CbfImeTextSpanList {
1762 items: if spans.is_empty() {
1763 ptr::null()
1764 } else {
1765 spans.as_ptr()
1766 },
1767 len: spans.len() as u32,
1768 };
1769 let (replacement_start, replacement_end) = ime_range_to_ffi(&composition.replacement_range);
1770
1771 let ffi_composition = CbfImeComposition {
1772 tab_id: 0,
1773 text: text.as_ptr(),
1774 selection_start: composition.selection_start,
1775 selection_end: composition.selection_end,
1776 replacement_range_start: replacement_start,
1777 replacement_range_end: replacement_end,
1778 spans: span_list,
1779 };
1780
1781 bridge_ok(
1782 "set_extension_popup_composition",
1783 bridge_call!(cbf_bridge_client_set_extension_popup_composition(
1784 self.inner,
1785 composition.popup_id.get(),
1786 &ffi_composition,
1787 )),
1788 )
1789 }
1790
1791 pub fn commit_text(&mut self, commit: &ChromeImeCommitText) -> Result<(), BridgeError> {
1793 self.ensure_ready()?;
1794
1795 let text = CString::new(commit.text.as_str()).map_err(|_| BridgeError::InvalidInput)?;
1796 let spans = to_ffi_ime_text_spans(&commit.spans);
1797 let span_list = CbfImeTextSpanList {
1798 items: if spans.is_empty() {
1799 ptr::null()
1800 } else {
1801 spans.as_ptr()
1802 },
1803 len: spans.len() as u32,
1804 };
1805 let (replacement_start, replacement_end) = ime_range_to_ffi(&commit.replacement_range);
1806
1807 let ffi_commit = CbfImeCommitText {
1808 tab_id: commit.browsing_context_id.get(),
1809 text: text.as_ptr(),
1810 relative_caret_position: commit.relative_caret_position,
1811 replacement_range_start: replacement_start,
1812 replacement_range_end: replacement_end,
1813 spans: span_list,
1814 };
1815
1816 bridge_ok(
1817 "commit_text",
1818 bridge_call!(cbf_bridge_client_commit_text(self.inner, &ffi_commit)),
1819 )
1820 }
1821
1822 pub fn commit_extension_popup_text(
1824 &mut self,
1825 commit: &ChromeTransientImeCommitText,
1826 ) -> Result<(), BridgeError> {
1827 self.ensure_ready()?;
1828
1829 let text = CString::new(commit.text.as_str()).map_err(|_| BridgeError::InvalidInput)?;
1830 let spans = to_ffi_ime_text_spans(&commit.spans);
1831 let span_list = CbfImeTextSpanList {
1832 items: if spans.is_empty() {
1833 ptr::null()
1834 } else {
1835 spans.as_ptr()
1836 },
1837 len: spans.len() as u32,
1838 };
1839 let (replacement_start, replacement_end) = ime_range_to_ffi(&commit.replacement_range);
1840
1841 let ffi_commit = CbfImeCommitText {
1842 tab_id: 0,
1843 text: text.as_ptr(),
1844 relative_caret_position: commit.relative_caret_position,
1845 replacement_range_start: replacement_start,
1846 replacement_range_end: replacement_end,
1847 spans: span_list,
1848 };
1849
1850 bridge_ok(
1851 "commit_extension_popup_text",
1852 bridge_call!(cbf_bridge_client_commit_extension_popup_text(
1853 self.inner,
1854 commit.popup_id.get(),
1855 &ffi_commit,
1856 )),
1857 )
1858 }
1859
1860 pub fn finish_composing_text(
1862 &mut self,
1863 browsing_context_id: TabId,
1864 behavior: ChromeConfirmCompositionBehavior,
1865 ) -> Result<(), BridgeError> {
1866 self.ensure_ready()?;
1867
1868 let behavior = match behavior {
1869 ChromeConfirmCompositionBehavior::DoNotKeepSelection => {
1870 CbfConfirmCompositionBehavior_kCbfConfirmCompositionDoNotKeepSelection
1871 }
1872 ChromeConfirmCompositionBehavior::KeepSelection => {
1873 CbfConfirmCompositionBehavior_kCbfConfirmCompositionKeepSelection
1874 }
1875 } as u8;
1876
1877 bridge_ok(
1878 "finish_composing_text",
1879 bridge_call!(cbf_bridge_client_finish_composing_text(
1880 self.inner,
1881 browsing_context_id.get(),
1882 behavior,
1883 )),
1884 )
1885 }
1886
1887 pub fn finish_extension_popup_composing_text(
1889 &mut self,
1890 popup_id: PopupId,
1891 behavior: ChromeConfirmCompositionBehavior,
1892 ) -> Result<(), BridgeError> {
1893 self.ensure_ready()?;
1894
1895 let behavior = match behavior {
1896 ChromeConfirmCompositionBehavior::DoNotKeepSelection => {
1897 CbfConfirmCompositionBehavior_kCbfConfirmCompositionDoNotKeepSelection
1898 }
1899 ChromeConfirmCompositionBehavior::KeepSelection => {
1900 CbfConfirmCompositionBehavior_kCbfConfirmCompositionKeepSelection
1901 }
1902 } as u8;
1903
1904 bridge_ok(
1905 "finish_extension_popup_composing_text",
1906 bridge_call!(cbf_bridge_client_finish_extension_popup_composing_text(
1907 self.inner,
1908 popup_id.get(),
1909 behavior,
1910 )),
1911 )
1912 }
1913
1914 pub fn set_extension_popup_focus(
1915 &mut self,
1916 popup_id: PopupId,
1917 focused: bool,
1918 ) -> Result<(), BridgeError> {
1919 self.ensure_ready()?;
1920
1921 bridge_ok(
1922 "set_extension_popup_focus",
1923 bridge_call!(cbf_bridge_client_set_extension_popup_focus(
1924 self.inner,
1925 popup_id.get(),
1926 focused
1927 )),
1928 )
1929 }
1930
1931 pub fn set_extension_popup_size(
1932 &mut self,
1933 popup_id: PopupId,
1934 width: u32,
1935 height: u32,
1936 ) -> Result<(), BridgeError> {
1937 self.ensure_ready()?;
1938
1939 bridge_ok(
1940 "set_extension_popup_size",
1941 bridge_call!(cbf_bridge_client_set_extension_popup_size(
1942 self.inner,
1943 popup_id.get(),
1944 width,
1945 height,
1946 )),
1947 )
1948 }
1949
1950 pub fn set_extension_popup_background_policy(
1952 &mut self,
1953 popup_id: PopupId,
1954 policy: ChromeBackgroundPolicy,
1955 ) -> Result<(), BridgeError> {
1956 self.ensure_ready()?;
1957
1958 let transparent = matches!(policy, ChromeBackgroundPolicy::Transparent);
1959
1960 bridge_ok(
1961 "set_extension_popup_background_policy",
1962 bridge_call!(cbf_bridge_client_set_extension_popup_background_policy(
1963 self.inner,
1964 popup_id.get(),
1965 transparent,
1966 )),
1967 )
1968 }
1969
1970 pub fn close_extension_popup(&mut self, popup_id: PopupId) -> Result<(), BridgeError> {
1971 self.ensure_ready()?;
1972
1973 bridge_ok(
1974 "close_extension_popup",
1975 bridge_call!(cbf_bridge_client_close_extension_popup(
1976 self.inner,
1977 popup_id.get()
1978 )),
1979 )
1980 }
1981
1982 pub fn execute_edit_action(
1984 &mut self,
1985 browsing_context_id: TabId,
1986 action: EditAction,
1987 ) -> Result<(), BridgeError> {
1988 self.ensure_ready()?;
1989
1990 bridge_ok(
1991 "execute_edit_action",
1992 bridge_call!(cbf_bridge_client_execute_edit_action(
1993 self.inner,
1994 browsing_context_id.get(),
1995 edit_action_to_ffi(action),
1996 )),
1997 )
1998 }
1999
2000 pub fn execute_extension_popup_edit_action(
2002 &mut self,
2003 popup_id: PopupId,
2004 action: EditAction,
2005 ) -> Result<(), BridgeError> {
2006 self.ensure_ready()?;
2007
2008 bridge_ok(
2009 "execute_extension_popup_edit_action",
2010 bridge_call!(cbf_bridge_client_execute_extension_popup_edit_action(
2011 self.inner,
2012 popup_id.get(),
2013 edit_action_to_ffi(action),
2014 )),
2015 )
2016 }
2017
2018 pub fn execute_context_menu_command(
2020 &mut self,
2021 menu_id: u64,
2022 command_id: i32,
2023 event_flags: i32,
2024 ) -> Result<(), BridgeError> {
2025 self.ensure_ready()?;
2026
2027 bridge_ok(
2028 "execute_context_menu_command",
2029 bridge_call!(cbf_bridge_client_execute_context_menu_command(
2030 self.inner,
2031 menu_id,
2032 command_id,
2033 event_flags,
2034 )),
2035 )
2036 }
2037
2038 pub fn accept_choice_menu_selection(
2040 &mut self,
2041 request_id: u64,
2042 indices: &[i32],
2043 ) -> Result<(), BridgeError> {
2044 self.ensure_ready()?;
2045
2046 let ffi_indices = CbfChoiceMenuSelectedIndices {
2047 items: indices.as_ptr(),
2048 len: indices.len() as u32,
2049 };
2050 bridge_ok(
2051 "accept_choice_menu_selection",
2052 bridge_call!(cbf_bridge_client_accept_choice_menu_selection(
2053 self.inner,
2054 request_id,
2055 &ffi_indices
2056 )),
2057 )
2058 }
2059
2060 pub fn dismiss_choice_menu(&mut self, request_id: u64) -> Result<(), BridgeError> {
2062 self.ensure_ready()?;
2063
2064 bridge_ok(
2065 "dismiss_choice_menu",
2066 bridge_call!(cbf_bridge_client_dismiss_choice_menu(
2067 self.inner, request_id
2068 )),
2069 )
2070 }
2071
2072 pub fn dismiss_context_menu(&mut self, menu_id: u64) -> Result<(), BridgeError> {
2074 self.ensure_ready()?;
2075
2076 bridge_ok(
2077 "dismiss_context_menu",
2078 bridge_call!(cbf_bridge_client_dismiss_context_menu(self.inner, menu_id)),
2079 )
2080 }
2081
2082 pub fn request_shutdown(&mut self, request_id: u64) -> Result<(), BridgeError> {
2084 self.ensure_ready()?;
2085
2086 bridge_ok(
2087 "request_shutdown",
2088 bridge_call!(cbf_bridge_client_request_shutdown(self.inner, request_id)),
2089 )
2090 }
2091
2092 pub fn confirm_shutdown(&mut self, request_id: u64, proceed: bool) -> Result<(), BridgeError> {
2094 self.ensure_ready()?;
2095
2096 bridge_ok(
2097 "confirm_shutdown",
2098 bridge_call!(cbf_bridge_client_confirm_shutdown(
2099 self.inner, request_id, proceed
2100 )),
2101 )
2102 }
2103
2104 pub fn force_shutdown(&mut self) -> Result<(), BridgeError> {
2106 self.ensure_ready()?;
2107
2108 bridge_ok(
2109 "force_shutdown",
2110 bridge_call!(cbf_bridge_client_force_shutdown(self.inner)),
2111 )
2112 }
2113
2114 pub fn shutdown(&mut self) {
2116 if !self.inner.is_null() {
2117 cleanup_bridge_call("shutdown bridge client", |bridge| {
2118 unsafe { bridge.cbf_bridge_client_shutdown(self.inner) };
2119 });
2120 }
2121 }
2122}
2123
2124impl IpcEventWaitHandle {
2125 pub(crate) fn wait_for_event(
2126 &self,
2127 timeout: Option<Duration>,
2128 ) -> Result<EventWaitResult, BridgeError> {
2129 wait_for_event_inner(self.inner, timeout)
2130 }
2131}
2132
2133fn wait_for_event_inner(
2134 inner: *mut CbfBridgeClientHandle,
2135 timeout: Option<Duration>,
2136) -> Result<EventWaitResult, BridgeError> {
2137 if inner.is_null() {
2138 return Ok(EventWaitResult::Closed);
2139 }
2140
2141 let timeout_ms = timeout
2142 .map(|value| value.as_millis().min(i64::MAX as u128) as i64)
2143 .unwrap_or(-1);
2144 let status = bridge_call!(cbf_bridge_client_wait_for_event(inner, timeout_ms));
2145
2146 match status {
2147 CbfBridgeEventWaitStatus_kCbfBridgeEventWaitStatusEventAvailable => {
2148 Ok(EventWaitResult::EventAvailable)
2149 }
2150 CbfBridgeEventWaitStatus_kCbfBridgeEventWaitStatusTimedOut => Ok(EventWaitResult::TimedOut),
2151 CbfBridgeEventWaitStatus_kCbfBridgeEventWaitStatusDisconnected => {
2152 Ok(EventWaitResult::Disconnected)
2153 }
2154 CbfBridgeEventWaitStatus_kCbfBridgeEventWaitStatusClosed => Ok(EventWaitResult::Closed),
2155 _ => Err(BridgeError::InvalidEvent),
2156 }
2157}
2158
2159fn edit_action_to_ffi(action: EditAction) -> u8 {
2160 (match action {
2161 EditAction::Undo => CbfEditAction_kCbfEditActionUndo,
2162 EditAction::Redo => CbfEditAction_kCbfEditActionRedo,
2163 EditAction::Cut => CbfEditAction_kCbfEditActionCut,
2164 EditAction::Copy => CbfEditAction_kCbfEditActionCopy,
2165 EditAction::Paste => CbfEditAction_kCbfEditActionPaste,
2166 EditAction::SelectAll => CbfEditAction_kCbfEditActionSelectAll,
2167 }) as u8
2168}
2169
2170fn as_ffi_string_ptr(value: &CString) -> *mut c_char {
2171 value.as_ptr().cast_mut()
2172}
2173
2174struct OwnedStringList {
2175 strings: Vec<CString>,
2176 ptrs: Vec<*mut c_char>,
2177}
2178
2179impl OwnedStringList {
2180 fn new(values: &[String]) -> Result<Self, BridgeError> {
2181 let strings = values
2182 .iter()
2183 .map(|value| CString::new(value.as_str()).map_err(|_| BridgeError::InvalidInput))
2184 .collect::<Result<Vec<_>, _>>()?;
2185 let ptrs = strings.iter().map(as_ffi_string_ptr).collect::<Vec<_>>();
2186 Ok(Self { strings, ptrs })
2187 }
2188
2189 fn as_ffi(&mut self) -> CbfStringList {
2190 let _ = &self.strings;
2191 CbfStringList {
2192 items: if self.ptrs.is_empty() {
2193 ptr::null_mut()
2194 } else {
2195 self.ptrs.as_mut_ptr()
2196 },
2197 len: self.ptrs.len() as u32,
2198 }
2199 }
2200}
2201
2202struct OwnedDragUrlList {
2203 strings: Vec<(CString, CString)>,
2204 items: Vec<CbfDragUrlInfo>,
2205}
2206
2207impl OwnedDragUrlList {
2208 fn new(values: &[crate::data::drag::ChromeDragUrlInfo]) -> Result<Self, BridgeError> {
2209 let strings = values
2210 .iter()
2211 .map(|value| {
2212 Ok((
2213 CString::new(value.url.as_str()).map_err(|_| BridgeError::InvalidInput)?,
2214 CString::new(value.title.as_str()).map_err(|_| BridgeError::InvalidInput)?,
2215 ))
2216 })
2217 .collect::<Result<Vec<_>, BridgeError>>()?;
2218 let items = strings
2219 .iter()
2220 .map(|(url, title)| CbfDragUrlInfo {
2221 url: as_ffi_string_ptr(url),
2222 title: as_ffi_string_ptr(title),
2223 })
2224 .collect();
2225 Ok(Self { strings, items })
2226 }
2227
2228 fn as_ffi(&self) -> CbfDragUrlInfoList {
2229 let _ = &self.strings;
2230 CbfDragUrlInfoList {
2231 items: if self.items.is_empty() {
2232 ptr::null()
2233 } else {
2234 self.items.as_ptr()
2235 },
2236 len: self.items.len() as u32,
2237 }
2238 }
2239}
2240
2241struct OwnedStringPairList {
2242 strings: Vec<(CString, CString)>,
2243 items: Vec<CbfStringPair>,
2244}
2245
2246impl OwnedStringPairList {
2247 fn new(values: &std::collections::BTreeMap<String, String>) -> Result<Self, BridgeError> {
2248 let strings = values
2249 .iter()
2250 .map(|(key, value)| {
2251 Ok((
2252 CString::new(key.as_str()).map_err(|_| BridgeError::InvalidInput)?,
2253 CString::new(value.as_str()).map_err(|_| BridgeError::InvalidInput)?,
2254 ))
2255 })
2256 .collect::<Result<Vec<_>, BridgeError>>()?;
2257 let items = strings
2258 .iter()
2259 .map(|(key, value)| CbfStringPair {
2260 key: as_ffi_string_ptr(key),
2261 value: as_ffi_string_ptr(value),
2262 })
2263 .collect();
2264 Ok(Self { strings, items })
2265 }
2266
2267 fn as_ffi(&mut self) -> CbfStringPairList {
2268 let _ = &self.strings;
2269 CbfStringPairList {
2270 items: if self.items.is_empty() {
2271 ptr::null_mut()
2272 } else {
2273 self.items.as_mut_ptr()
2274 },
2275 len: self.items.len() as u32,
2276 }
2277 }
2278}
2279
2280struct OwnedDragData {
2281 text: CString,
2282 html: CString,
2283 html_base_url: CString,
2284 url_infos: OwnedDragUrlList,
2285 filenames: OwnedStringList,
2286 file_mime_types: OwnedStringList,
2287 custom_data: OwnedStringPairList,
2288}
2289
2290impl OwnedDragData {
2291 fn new(data: &ChromeDragData) -> Result<Self, BridgeError> {
2292 Ok(Self {
2293 text: CString::new(data.text.as_str()).map_err(|_| BridgeError::InvalidInput)?,
2294 html: CString::new(data.html.as_str()).map_err(|_| BridgeError::InvalidInput)?,
2295 html_base_url: CString::new(data.html_base_url.as_str())
2296 .map_err(|_| BridgeError::InvalidInput)?,
2297 url_infos: OwnedDragUrlList::new(&data.url_infos)?,
2298 filenames: OwnedStringList::new(&data.filenames)?,
2299 file_mime_types: OwnedStringList::new(&data.file_mime_types)?,
2300 custom_data: OwnedStringPairList::new(&data.custom_data)?,
2301 })
2302 }
2303
2304 fn as_ffi(&mut self) -> CbfDragData {
2305 CbfDragData {
2306 text: as_ffi_string_ptr(&self.text),
2307 html: as_ffi_string_ptr(&self.html),
2308 html_base_url: as_ffi_string_ptr(&self.html_base_url),
2309 url_infos: self.url_infos.as_ffi(),
2310 filenames: self.filenames.as_ffi(),
2311 file_mime_types: self.file_mime_types.as_ffi(),
2312 custom_data: self.custom_data.as_ffi(),
2313 }
2314 }
2315}
2316
2317fn ipc_message_type_to_ffi(message_type: TabIpcMessageType) -> u8 {
2318 (match message_type {
2319 TabIpcMessageType::Request => CbfIpcMessageType_kCbfIpcMessageRequest,
2320 TabIpcMessageType::Response => CbfIpcMessageType_kCbfIpcMessageResponse,
2321 TabIpcMessageType::Event => CbfIpcMessageType_kCbfIpcMessageEvent,
2322 }) as u8
2323}
2324
2325fn ipc_error_code_to_ffi(error_code: TabIpcErrorCode) -> u8 {
2326 (match error_code {
2327 TabIpcErrorCode::Timeout => CbfIpcErrorCode_kCbfIpcErrorTimeout,
2328 TabIpcErrorCode::Aborted => CbfIpcErrorCode_kCbfIpcErrorAborted,
2329 TabIpcErrorCode::Disconnected => CbfIpcErrorCode_kCbfIpcErrorDisconnected,
2330 TabIpcErrorCode::IpcDisabled => CbfIpcErrorCode_kCbfIpcErrorIpcDisabled,
2331 TabIpcErrorCode::ContextClosed => CbfIpcErrorCode_kCbfIpcErrorContextClosed,
2332 TabIpcErrorCode::RemoteError => CbfIpcErrorCode_kCbfIpcErrorRemoteError,
2333 TabIpcErrorCode::ProtocolError => CbfIpcErrorCode_kCbfIpcErrorProtocolError,
2334 }) as u8
2335}
2336
2337impl Drop for IpcClient {
2338 fn drop(&mut self) {
2339 if !self.inner.is_null() {
2340 cleanup_bridge_call("destroy bridge client on drop", |bridge| {
2341 unsafe { bridge.cbf_bridge_client_destroy(self.inner) };
2342 });
2343 self.inner = ptr::null_mut();
2344 }
2345 }
2346}
2347
2348impl IpcClient {
2349 fn ensure_ready(&self) -> Result<(), BridgeError> {
2350 if self.inner.is_null() {
2351 Err(BridgeError::InvalidState)
2352 } else {
2353 Ok(())
2354 }
2355 }
2356}
2357
2358fn bridge_api() -> Result<&'static BridgeLibrary, BridgeError> {
2359 bridge().map_err(map_bridge_load_error)
2360}
2361
2362fn map_bridge_load_error(_: BridgeLoadError) -> BridgeError {
2363 BridgeError::BridgeLoadFailed
2364}
2365
2366fn parse_channel_switch_arg(buf: &[u8]) -> Result<String, BridgeError> {
2367 let switch_arg = CStr::from_bytes_until_nul(buf)
2368 .map_err(|_| BridgeError::InvalidChannelArgument)?
2369 .to_str()
2370 .map_err(|_| BridgeError::InvalidChannelArgument)?
2371 .to_owned();
2372 if switch_arg.is_empty() {
2373 return Err(BridgeError::InvalidChannelArgument);
2374 }
2375
2376 Ok(switch_arg)
2377}
2378
2379fn authentication_result(success: bool) -> Result<(), BridgeError> {
2380 if success {
2381 Ok(())
2382 } else {
2383 Err(BridgeError::AuthenticationFailed)
2384 }
2385}
2386
2387fn bridge_ok(operation: &'static str, success: bool) -> Result<(), BridgeError> {
2388 if success {
2389 Ok(())
2390 } else {
2391 Err(BridgeError::OperationFailed { operation })
2392 }
2393}
2394
2395fn cleanup_bridge_call<F>(operation: &'static str, callback: F)
2396where
2397 F: FnOnce(&BridgeLibrary),
2398{
2399 if let Err(error) = bridge().map(callback) {
2400 warn!(operation, error = ?error, "bridge cleanup call failed");
2401 }
2402}
2403
2404#[cfg(test)]
2405mod tests {
2406 use std::mem::MaybeUninit;
2407
2408 use super::{
2409 BridgeError, IpcClient, authentication_result, bridge_ok, execution_state_to_ffi,
2410 parse_channel_switch_arg,
2411 };
2412 use crate::data::execution::ChromeTabExecutionState;
2413
2414 fn null_ipc_client() -> IpcClient {
2415 unsafe { MaybeUninit::zeroed().assume_init() }
2418 }
2419
2420 #[test]
2421 fn parse_channel_switch_arg_rejects_missing_nul() {
2422 assert_eq!(
2423 parse_channel_switch_arg(b"--cbf-ipc-handle=abc"),
2424 Err(BridgeError::InvalidChannelArgument)
2425 );
2426 }
2427
2428 #[test]
2429 fn parse_channel_switch_arg_rejects_invalid_utf8() {
2430 assert_eq!(
2431 parse_channel_switch_arg(b"--cbf-ipc-handle=\xFF\0"),
2432 Err(BridgeError::InvalidChannelArgument)
2433 );
2434 }
2435
2436 #[test]
2437 fn parse_channel_switch_arg_rejects_empty_string() {
2438 assert_eq!(
2439 parse_channel_switch_arg(b"\0"),
2440 Err(BridgeError::InvalidChannelArgument)
2441 );
2442 }
2443
2444 #[test]
2445 fn null_client_handle_reports_invalid_state() {
2446 let client = null_ipc_client();
2447
2448 assert_eq!(client.ensure_ready(), Err(BridgeError::InvalidState));
2449 }
2450
2451 #[test]
2452 fn authentication_result_reports_authentication_failure() {
2453 assert_eq!(
2454 authentication_result(false),
2455 Err(BridgeError::AuthenticationFailed)
2456 );
2457 }
2458
2459 #[test]
2460 fn bridge_ok_reports_operation_name() {
2461 assert_eq!(
2462 bridge_ok("navigate", false),
2463 Err(BridgeError::OperationFailed {
2464 operation: "navigate",
2465 })
2466 );
2467 }
2468
2469 #[test]
2470 fn execution_state_to_ffi_encodes_expected_values() {
2471 assert_eq!(
2472 execution_state_to_ffi(ChromeTabExecutionState::Running),
2473 super::CbfTabExecutionState_kCbfTabExecutionStateRunning as u8
2474 );
2475 assert_eq!(
2476 execution_state_to_ffi(ChromeTabExecutionState::Suspended),
2477 super::CbfTabExecutionState_kCbfTabExecutionStateSuspended as u8
2478 );
2479 }
2480}