1use std::collections::HashMap;
6use std::sync::Arc;
7use std::sync::{
8 Mutex,
9 atomic::{AtomicBool, Ordering},
10 mpsc::{self, Receiver, Sender},
11};
12
13use cef::*;
14use sha2::{Digest, Sha256};
15use tauri_runtime::{
16 Cookie, Error, Result, Runtime, UserEvent, WebviewDispatch, WebviewEventId,
17 dpi::{PhysicalPosition, PhysicalSize, Position, Rect, Size},
18 webview::{
19 DetachedWebview, InitializationScript, PendingWebview, UriSchemeProtocolHandler,
20 WebviewAttributes,
21 },
22 window::{WebviewEvent, WindowId},
23};
24use tauri_utils::{
25 Theme,
26 config::{BackgroundThrottlingPolicy, Color},
27 html::normalize_script_for_csp,
28};
29use url::Url;
30
31use crate::cef_impl::{client as browser_client, cookie, request_context, request_handler};
32use crate::runtime::{CefRuntime, Message, RuntimeContext, WinitCefApp};
33use crate::window::AppWindow;
34
35#[derive(Clone)]
40pub struct Webview {
41 browser: cef::Browser,
42 snapshot: WebviewSnapshot,
43 frame_navigation_state: crate::FrameNavigationState,
44 popups: Vec<Webview>,
45 opener: Option<crate::FrameNavigationState>,
46}
47
48#[derive(Clone, Debug)]
52#[non_exhaustive]
53pub struct WebviewSnapshot {
54 pub browser_id: i32,
56 pub dialogs: crate::NativeDialogObservation,
58 pub document: Option<crate::NativeDocumentToken>,
61 pub window_label: Option<String>,
63 pub window: Option<crate::NativeWindowToken>,
67 pub parent_matches: Option<bool>,
74 pub bounds: Option<Rect>,
77 pub visible: Option<bool>,
81}
82
83impl Webview {
84 pub(crate) fn new(
85 browser: cef::Browser,
86 snapshot: WebviewSnapshot,
87 frame_navigation_state: crate::FrameNavigationState,
88 ) -> Self {
89 Self {
90 browser,
91 snapshot,
92 frame_navigation_state,
93 popups: Vec::new(),
94 opener: None,
95 }
96 }
97
98 pub fn snapshot(&self) -> &WebviewSnapshot {
101 &self.snapshot
102 }
103
104 pub fn frame_navigation_state(&self) -> &crate::FrameNavigationState {
107 &self.frame_navigation_state
108 }
109
110 pub(crate) fn set_opener(&mut self, opener: crate::FrameNavigationState) {
111 self.opener = Some(opener);
112 }
113
114 pub fn popups(&self) -> &[Webview] {
117 &self.popups
118 }
119
120 pub fn opener(&self) -> Option<&crate::FrameNavigationState> {
122 self.opener.as_ref()
123 }
124
125 pub fn for_document(&self, document: &crate::NativeDocumentToken) -> Option<&Webview> {
128 std::iter::once(self)
129 .chain(self.popups.iter())
130 .find(|view| view.snapshot.document.as_ref() == Some(document))
131 }
132
133 pub fn browser(&self) -> cef::Browser {
138 self.browser.clone()
139 }
140}
141
142pub fn webview_version() -> tauri_runtime::Result<String> {
143 Ok(crate::downgrade::ChromiumVersion::embedded().to_string())
144}
145
146#[inline]
147fn color_to_argb(color: Color) -> u32 {
148 let (r, g, b, a) = color.into();
149 ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
150}
151
152fn browser_settings_from_webview_attributes(
191 webview_attributes: &WebviewAttributes,
192 offscreen: bool,
193) -> cef::BrowserSettings {
194 cef::BrowserSettings {
195 javascript: cef::State::from(if webview_attributes.javascript_disabled {
196 cef::sys::cef_state_t::STATE_DISABLED
197 } else {
198 cef::sys::cef_state_t::STATE_ENABLED
199 }),
200 javascript_access_clipboard: cef::State::from(if webview_attributes.clipboard {
201 cef::sys::cef_state_t::STATE_ENABLED
202 } else {
203 cef::sys::cef_state_t::STATE_DISABLED
204 }),
205 background_color: match webview_attributes.background_color {
206 Some(color) => color_to_argb(color),
207 None if offscreen && !webview_attributes.transparent => 0xFFFF_FFFF,
208 None => 0,
209 },
210 chrome_status_bubble: cef::State::from(cef::sys::cef_state_t::STATE_DISABLED),
216 chrome_zoom_bubble: cef::State::from(cef::sys::cef_state_t::STATE_DISABLED),
217 ..Default::default()
218 }
219}
220
221fn warn_about_unsupported_attributes(label: &str, attributes: &WebviewAttributes, offscreen: bool) {
228 if attributes.additional_browser_args.is_some() {
229 log::warn!(
230 "webview {label:?} sets additional_browser_args, which the CEF runtime does not support: \
231 Chromium's command line is per process, not per webview. Use `Cef::command_line_arg` \
232 to pass switches to the browser process."
233 );
234 }
235 if attributes.transparent && !offscreen {
236 log::warn!(
237 "webview {label:?} asks to be transparent, which the CEF runtime does not support: a \
238 windowed Chromium browser paints an opaque background, and transparent painting exists \
239 only in off-screen rendering."
240 );
241 }
242 if attributes.accept_first_mouse {
243 log::warn!(
244 "webview {label:?} sets accept_first_mouse, which the CEF runtime does not support: \
245 Chromium's own view decides whether the click that activates a window reaches the page."
246 );
247 }
248 if attributes.browser_extensions_enabled || attributes.extensions_path.is_some() {
249 log::warn!(
250 "webview {label:?} asks for browser extensions, which the CEF runtime does not support: \
251 CEF removed its extension loading API."
252 );
253 }
254 if let Some(policy) = &attributes.background_throttling
255 && !matches!(policy, BackgroundThrottlingPolicy::Throttle)
256 {
257 log::warn!(
258 "webview {label:?} sets a background throttling policy, which the CEF runtime cannot \
259 apply per webview: Chromium throttles hidden pages process-wide (its default is the \
260 `throttle` policy). Pass `--disable-background-timer-throttling` through \
261 `Cef::command_line_arg` to turn it off for every webview."
262 );
263 }
264 #[cfg(windows)]
267 if !matches!(
268 attributes.scroll_bar_style,
269 tauri_runtime::webview::ScrollBarStyle::Default
270 ) {
271 log::warn!(
272 "webview {label:?} asks for overlay scrollbars, which the CEF runtime cannot apply per \
273 webview: Chromium's overlay scrollbars are a process-wide feature. Enable it for every \
274 webview with `Cef::enable_features([\"OverlayScrollbar\"])`."
275 );
276 }
277}
278
279#[derive(Debug, Clone)]
288pub enum DevToolsProtocol {
289 Message(Vec<u8>),
291 Event { method: String, params: Vec<u8> },
293 MethodResult {
301 message_id: i32,
302 success: bool,
303 result: Vec<u8>,
304 },
305}
306
307pub(crate) type DevToolsProtocolHandler = dyn Fn(DevToolsProtocol) + Send + Sync;
308
309#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
321#[non_exhaustive]
322pub enum ChromeCommandGroup {
323 WindowAndTab,
329 Document,
336 BrowserChrome,
343 BrowserSurface,
351 History,
358}
359
360impl ChromeCommandGroup {
361 pub(crate) const ALL: &'static [Self] = &[
364 Self::WindowAndTab,
365 Self::Document,
366 Self::BrowserChrome,
367 Self::BrowserSurface,
368 Self::History,
369 ];
370}
371
372#[derive(Clone, Debug)]
377#[non_exhaustive]
378pub struct ConsoleMessage {
379 pub level: ConsoleMessageLevel,
381 pub message: String,
384 pub source: String,
386 pub line: i32,
388}
389
390#[derive(Clone, Copy, Debug, PartialEq, Eq)]
392#[non_exhaustive]
393pub enum ConsoleMessageLevel {
394 Verbose,
396 Info,
399 Warning,
401 Error,
404 Fatal,
406 Other,
408}
409
410pub type ConsoleMessageHandler = dyn Fn(ConsoleMessage) + Send + Sync + 'static;
412
413pub type BrowserSettingsCallback = dyn Fn(&mut cef::BrowserSettings) + Send + Sync + 'static;
415
416impl ConsoleMessage {
417 pub(crate) fn from_cef(
418 level: cef::LogSeverity,
419 message: Option<&CefString>,
420 source: Option<&CefString>,
421 line: i32,
422 ) -> Self {
423 use cef::sys::cef_log_severity_t;
424
425 Self {
426 level: match cef_log_severity_t::from(level) {
427 cef_log_severity_t::LOGSEVERITY_VERBOSE => ConsoleMessageLevel::Verbose,
428 cef_log_severity_t::LOGSEVERITY_DEFAULT | cef_log_severity_t::LOGSEVERITY_INFO => {
429 ConsoleMessageLevel::Info
430 }
431 cef_log_severity_t::LOGSEVERITY_WARNING => ConsoleMessageLevel::Warning,
432 cef_log_severity_t::LOGSEVERITY_ERROR => ConsoleMessageLevel::Error,
433 cef_log_severity_t::LOGSEVERITY_FATAL => ConsoleMessageLevel::Fatal,
434 _ => ConsoleMessageLevel::Other,
435 },
436 message: message.map(ToString::to_string).unwrap_or_default(),
437 source: source.map(ToString::to_string).unwrap_or_default(),
438 line,
439 }
440 }
441}
442pub(crate) type WebviewEventHandler = Box<dyn Fn(&WebviewEvent) + Send>;
443pub(crate) type WebviewEventListeners = Arc<Mutex<HashMap<WebviewEventId, WebviewEventHandler>>>;
444
445pub(crate) enum WebviewMessage {
446 AddEventListener(WebviewEventId, Box<dyn Fn(&WebviewEvent) + Send>),
447 EvaluateScript(String),
448 EvaluateScriptWithCallback(String, Box<dyn Fn(String) + Send + 'static>),
449 Navigate(Url),
450 Reload,
451 GoBack,
452 CanGoBack(Sender<Result<bool>>),
453 GoForward,
454 CanGoForward(Sender<Result<bool>>),
455 Print,
456 Close,
457 Show,
458 Hide,
459 SetPosition(Position),
460 SetSize(Size),
461 SetBounds(Rect),
462 SetFocus,
463 Reparent(WindowId, Sender<Result<()>>),
464 SetAutoResize(bool),
465 SetZoom(f64),
466 SetBackgroundColor(Option<Color>),
467 ClearAllBrowsingData,
468 Url(Sender<Result<String>>),
469 Bounds(Sender<Result<Rect>>),
470 Position(Sender<Result<PhysicalPosition<i32>>>),
471 Size(Sender<Result<PhysicalSize<u32>>>),
472 WithWebview(Box<dyn FnOnce(Webview) + Send>),
473 CookiesForUrl(Url, Sender<Result<Vec<Cookie<'static>>>>),
474 Cookies(Sender<Result<Vec<Cookie<'static>>>>),
475 SetCookie(Cookie<'static>),
476 DeleteCookie(Cookie<'static>),
477 #[cfg(any(debug_assertions, feature = "devtools"))]
478 OpenDevTools,
479 #[cfg(any(debug_assertions, feature = "devtools"))]
480 CloseDevTools,
481 #[cfg(any(debug_assertions, feature = "devtools"))]
482 IsDevToolsOpen(Sender<bool>),
483 SendDevToolsMessage(Vec<u8>, Sender<Result<()>>),
484 OnDevToolsProtocol(Arc<DevToolsProtocolHandler>, Sender<Result<()>>),
485}
486
487#[derive(Clone, Copy)]
490pub(crate) struct BoundsRate {
491 pub(crate) x: f32,
492 pub(crate) y: f32,
493 pub(crate) width: f32,
494 pub(crate) height: f32,
495}
496
497impl Default for BoundsRate {
498 fn default() -> Self {
499 Self {
500 x: 0.,
501 y: 0.,
502 width: 1.,
503 height: 1.,
504 }
505 }
506}
507
508pub(crate) struct AppWebview {
509 pub(crate) webview_id: u32,
510 pub(crate) label: String,
511 pub(crate) browser: cef::Browser,
512 pub(crate) browser_id: i32,
513 pub(crate) frame_navigation_state: crate::FrameNavigationState,
514 pub(crate) popup_family: Arc<crate::popup::PopupFamily>,
515 pub(crate) dialogs: crate::dialog::DialogState,
516 pub(crate) host: cef::BrowserHost,
517 pub(crate) uri_scheme_protocols: Arc<HashMap<String, Arc<Box<UriSchemeProtocolHandler>>>>,
518 pub(crate) devtools_protocol_handlers: Arc<Mutex<Vec<Arc<DevToolsProtocolHandler>>>>,
519 pub(crate) devtools_observer_registration: Arc<Mutex<Option<cef::Registration>>>,
521 pub(crate) devtools_enabled: bool,
524 pub(crate) listeners: WebviewEventListeners,
525 pub(crate) bounds_rate: Option<BoundsRate>,
526 pub(crate) offscreen: Option<crate::OffscreenView>,
527}
528
529impl AppWebview {
530 fn resize(&self, scale: f64, x: i32, y: i32, width: i32, height: i32) {
531 if let Some(view) = &self.offscreen {
532 *view.bounds.lock().unwrap_or_else(|e| e.into_inner()) = Rect {
533 position: tauri_runtime::dpi::LogicalPosition::new(x as f64 / scale, y as f64 / scale)
534 .into(),
535 size: tauri_runtime::dpi::LogicalSize::new(
536 width.max(0) as f64 / scale,
537 height.max(0) as f64 / scale,
538 )
539 .into(),
540 };
541 } else {
542 self.apply_physical_bounds(scale, x, y, width, height);
543 }
544 }
545
546 pub(crate) fn set_bounds(&mut self, parent_size: PhysicalSize<u32>, scale: f64, bounds: Rect) {
547 let position = bounds.position.to_physical::<i32>(scale);
548 let size = bounds.size.to_physical::<u32>(scale);
549
550 let x = position.x;
551 let y = position.y;
552 let w = size.width as i32;
553 let h = size.height as i32;
554
555 if self.bounds_rate.is_some() {
556 let win_w = parent_size.width.max(1) as f32;
557 let win_h = parent_size.height.max(1) as f32;
558 self.bounds_rate = Some(BoundsRate {
559 x: x as f32 / win_w,
560 y: y as f32 / win_h,
561 width: w as f32 / win_w,
562 height: h as f32 / win_h,
563 });
564 }
565
566 self.host.notify_move_or_resize_started();
567 self.resize(scale, x, y, w, h);
568 self.host.was_resized();
569 }
570
571 pub(crate) fn set_visible(&self, visible: bool) {
572 self.host.was_hidden(if visible { 0 } else { 1 });
573 if let Some(view) = &self.offscreen {
574 view.visible.store(visible, Ordering::Relaxed);
575 view.request_redraw();
576 } else {
577 self.apply_visible(visible);
578 }
579 }
580
581 pub fn url(&self) -> Option<String> {
582 self
583 .browser
584 .main_frame()
585 .map(|frame| cef::CefString::from(&frame.url()).to_string())
586 }
587}
588
589impl<T: UserEvent> WinitCefApp<T> {
590 pub(crate) fn create_webview(
591 &mut self,
592 window_id: WindowId,
593 webview_id: u32,
594 pending: PendingWebview<T, CefRuntime<T>>,
595 ) -> Result<()> {
596 let Self {
597 context,
598 scheme_registry,
599 state,
600 ..
601 } = self;
602 let Some(appwindow) = state.windows.get_mut(&window_id) else {
603 return Err(Error::CreateWebview(
604 format!("window {window_id:?} does not exist").into(),
605 ));
606 };
607 Self::build_and_attach_webview(
608 context,
609 scheme_registry,
610 &mut state.live_browsers,
611 appwindow,
612 webview_id,
613 browser_client::DragDropEventTarget::Webview,
614 pending,
615 )
616 }
617
618 pub(crate) fn build_and_attach_webview(
622 context: &RuntimeContext<T>,
623 scheme_registry: &request_handler::SchemeRegistry,
624 live_browsers: &mut usize,
625 appwindow: &mut AppWindow,
626 webview_id: u32,
627 drag_drop_event_target: browser_client::DragDropEventTarget,
628 pending: PendingWebview<T, CefRuntime<T>>,
629 ) -> Result<()> {
630 let offscreen = pending
633 .runtime_specific_attributes
634 .offscreen
635 .as_ref()
636 .map(|_| {
637 let scale = appwindow.window.scale_factor();
638 let bounds = pending
639 .webview_attributes
640 .bounds
641 .unwrap_or_else(|| Rect {
642 position: PhysicalPosition::new(0, 0).into(),
643 size: appwindow.safe_surface_size().into(),
644 })
645 .to_logical::<f64, f64>(scale);
646 crate::OffscreenView {
647 window: Arc::downgrade(&appwindow.window),
648 bounds: Arc::new(Mutex::new(Rect {
649 position: bounds.position.into(),
650 size: bounds.size.into(),
651 })),
652 visible: Arc::new(AtomicBool::new(true)),
653 }
654 });
655 let parent = appwindow.cef_host_handle();
656 let parent_size = appwindow.safe_surface_size();
657 let scale = appwindow.window.scale_factor();
658 let app_wide_theme = *context.app_wide_theme.lock().unwrap();
659 let theme = appwindow.resolved_theme(app_wide_theme);
660 let Some(child) = Self::build_browser_child(
661 context,
662 scheme_registry,
663 appwindow.id,
664 webview_id,
665 parent,
666 parent_size,
667 scale,
668 theme,
669 drag_drop_event_target,
670 offscreen,
671 pending,
672 ) else {
673 return Err(Error::CreateWebview(
674 "failed to create CEF browser".to_string().into(),
675 ));
676 };
677
678 #[cfg(windows)]
683 if child.offscreen.is_none() {
684 child.raise_to_top();
685 } else {
686 appwindow.background_surface = None;
687 }
688
689 *live_browsers += 1;
690 appwindow.children.push(child);
691 layout_app_window(appwindow);
692 Ok(())
693 }
694
695 pub(crate) fn build_browser_child(
696 context: &RuntimeContext<T>,
697 scheme_registry: &request_handler::SchemeRegistry,
698 window_id: WindowId,
699 webview_id: u32,
700 parent: cef::sys::cef_window_handle_t,
701 parent_size: PhysicalSize<u32>,
702 scale: f64,
703 theme: Option<Theme>,
704 drag_drop_event_target: browser_client::DragDropEventTarget,
705 offscreen: Option<crate::OffscreenView>,
706 mut pending: PendingWebview<T, CefRuntime<T>>,
707 ) -> Option<AppWebview> {
708 let bounds_rate = compute_child_bounds_rate(
709 pending.webview_attributes.bounds.as_ref(),
710 pending.webview_attributes.auto_resize,
711 parent_size,
712 scale,
713 );
714 let devtools_enabled = context.devtools_allowed
715 && (cfg!(debug_assertions) || cfg!(feature = "devtools"))
716 && pending.webview_attributes.devtools.unwrap_or(true);
717
718 #[cfg(any(debug_assertions, feature = "devtools"))]
724 if devtools_enabled
725 && (offscreen.is_some() || is_alloy_style(pending.runtime_specific_attributes.runtime_style))
726 {
727 pending.webview_attributes.initialization_scripts.push(
728 tauri_runtime::webview::InitializationScript {
729 script: tauri_runtime::webview::devtools_shortcut_script(),
730 for_main_frame_only: true,
731 },
732 );
733 }
734
735 let initialization_scripts = initialization_scripts(&mut pending.webview_attributes);
736 let uri_scheme_protocols: Arc<HashMap<_, _>> = Arc::new(
737 pending
738 .uri_scheme_protocols
739 .into_iter()
740 .map(|(scheme, handler)| (scheme, Arc::new(handler)))
741 .collect(),
742 );
743 let on_page_load_handler = pending.on_page_load_handler.take().map(Arc::from);
744 let document_title_changed_handler =
745 pending.document_title_changed_handler.take().map(Arc::from);
746 let zoom_hotkeys_enabled = pending.webview_attributes.zoom_hotkeys_enabled;
747 let allowed_chrome_commands = pending
748 .runtime_specific_attributes
749 .allowed_chrome_commands
750 .clone();
751 let drag_drop_handler_enabled = pending.webview_attributes.drag_drop_handler_enabled;
752 let drag_drop_state = Arc::new(Mutex::new(browser_client::DragDropState::default()));
753 let web_content_process_terminate_handler = pending
754 .on_web_content_process_terminate_handler
755 .take()
756 .map(Arc::from);
757 let frame_navigation_state = crate::FrameNavigationState::new();
758 let popup_family = Arc::new(crate::popup::PopupFamily::new(
759 frame_navigation_state.clone(),
760 ));
761 let dialogs = crate::dialog::DialogState::new(frame_navigation_state.clone());
762 let frame_state_for_events = frame_navigation_state.clone();
763 let frame_event_handler = pending
764 .runtime_specific_attributes
765 .frame_event_handler
766 .clone();
767 let handlers = browser_client::TauriCefBrowserClientHandlers {
768 frame_event_handler: Some(Arc::new(move |event| {
774 frame_state_for_events.on_frame_event(&event);
775 if frame_state_for_events.has_browser_id(event.browser_id)
776 && let Some(handler) = &frame_event_handler
777 {
778 handler(event);
779 }
780 })),
781 ipc_handler: pending.ipc_handler.map(Arc::from),
782 on_page_load_handler,
783 document_title_changed_handler,
784 navigation_handler: pending.navigation_handler.map(Arc::from),
785 new_window_handler: pending.new_window_handler.map(Arc::from),
786 download_handler: pending.download_handler.take(),
787 console_message_handler: pending
788 .runtime_specific_attributes
789 .console_message_handler
790 .clone(),
791 permission_request_handler: pending.permission_request_handler.take().map(Arc::from),
792 web_content_process_terminate_handler,
793 };
794
795 let mut client =
796 browser_client::TauriCefBrowserClient::build(browser_client::TauriCefBrowserClientArgs {
797 context: context.clone(),
798 window_id,
799 webview_id,
800 label: pending.label.clone(),
801 initial_url: Some(pending.url.as_str().to_string()),
802 devtools_enabled,
803 zoom_hotkeys_enabled,
804 allowed_chrome_commands,
805 drag_drop_event_target,
806 drag_drop_handler_enabled,
807 drag_drop_state,
808 frame_navigation_state: frame_navigation_state.clone(),
809 popup_family: Arc::downgrade(&popup_family),
810 opener: None,
811 render_handler: offscreen
812 .as_ref()
813 .zip(pending.runtime_specific_attributes.offscreen.as_ref())
814 .map(|(view, options)| (options.render_handler)(view.clone())),
815 cursor_handler: pending
816 .runtime_specific_attributes
817 .offscreen
818 .as_ref()
819 .and_then(|options| options.cursor_handler.clone()),
820 handlers,
821 sender: context.sender.clone(),
822 });
823
824 let bounds = pending.webview_attributes.bounds.unwrap_or_else(|| Rect {
827 position: PhysicalPosition::new(0, 0).into(),
828 size: parent_size.into(),
829 });
830 #[cfg(not(target_os = "macos"))]
831 let bounds = bounds.to_physical::<i32, i32>(scale);
832 #[cfg(target_os = "macos")]
833 let bounds = bounds.to_logical::<i32, i32>(scale);
834 let bounds = cef::Rect {
835 x: bounds.position.x,
836 y: bounds.position.y,
837 width: bounds.size.width,
838 height: bounds.size.height,
839 };
840
841 let cef_runtime_style = match pending.runtime_specific_attributes.runtime_style {
843 Some(RuntimeStyle::Alloy) => cef::RuntimeStyle::ALLOY,
844 Some(RuntimeStyle::Chrome) => cef::RuntimeStyle::CHROME,
845 None => cef::RuntimeStyle::DEFAULT,
846 };
847
848 let window_info = if let Some(options) = &pending.runtime_specific_attributes.offscreen {
849 options.window_info(parent)
850 } else {
851 let mut info = cef::WindowInfo::default().set_as_child(parent, &bounds);
852 info.runtime_style = cef_runtime_style;
853 info
854 };
855 let mut settings =
856 browser_settings_from_webview_attributes(&pending.webview_attributes, offscreen.is_some());
857 if let Some(callback) = &pending
859 .runtime_specific_attributes
860 .browser_settings_callback
861 {
862 callback(&mut settings);
863 }
864 let settings = settings;
865 let user_agent = pending.webview_attributes.user_agent.clone();
870
871 warn_about_unsupported_attributes(
872 &pending.label,
873 &pending.webview_attributes,
874 offscreen.is_some(),
875 );
876
877 let custom_protocol_scheme = if pending.webview_attributes.use_https_scheme {
878 "https"
879 } else {
880 "http"
881 }
882 .to_string();
883 let custom_scheme_domain_names: Vec<String> = uri_scheme_protocols
884 .keys()
885 .map(|scheme| format!("{scheme}.localhost"))
886 .collect();
887 let real_initial_url = pending.url.as_str().to_string();
888 let (browser_tx, browser_rx) = mpsc::channel();
889 let (init_done, on_initialized) = request_context::deferred_init_continuation({
890 let scheme_registry = scheme_registry.clone();
891 let uri_scheme_protocols = uri_scheme_protocols.clone();
892 let initialization_scripts = initialization_scripts.clone();
893 let custom_protocol_scheme = custom_protocol_scheme.clone();
894 let custom_scheme_domain_names = custom_scheme_domain_names.clone();
895 let label = pending.label.clone();
896 move |mut request_context| {
897 request_context::apply_theme_scheme(request_context.as_ref(), theme);
898
899 let initial_url = CefString::from(INITIAL_LOAD_URL);
902 let Some(browser) = cef::browser_host_create_browser_sync(
903 Some(&window_info),
904 Some(&mut client),
905 Some(&initial_url),
906 Some(&settings),
907 None,
908 request_context.as_mut(),
909 ) else {
910 log::error!("failed to create CEF browser for webview {label:?}");
911 return;
912 };
913 let Some(host) = browser.host() else {
914 log::error!("CEF browser for webview {label:?} has no host");
915 return;
916 };
917 let browser_id = browser.identifier();
918
919 {
920 let mut registry = scheme_registry.lock().unwrap();
921 for (scheme, handler) in uri_scheme_protocols.iter() {
922 registry.insert(
923 (browser_id, scheme.clone()),
924 (
925 label.clone(),
926 handler.clone(),
927 initialization_scripts.clone(),
928 ),
929 );
930 }
931 }
932
933 let devtools_protocol_handlers: Arc<Mutex<Vec<Arc<DevToolsProtocolHandler>>>> =
942 Arc::default();
943 let pending_initial_loads: PendingInitialLoads = Arc::new(Mutex::new(HashMap::new()));
944 let devtools_observer_registration = Arc::new(Mutex::new(add_dev_tools_observer(
945 &browser,
946 devtools_protocol_handlers.clone(),
947 pending_initial_loads.clone(),
948 dialogs.clone(),
949 )));
950 if let Some(user_agent) = &user_agent {
952 apply_user_agent_override(&host, user_agent);
953 }
954 load_initial_url_after_registering_initialization_scripts(
955 &browser,
956 &initialization_scripts,
957 &custom_protocol_scheme,
958 &custom_scheme_domain_names,
959 &real_initial_url,
960 &pending_initial_loads,
961 );
962
963 browser_tx
964 .send(AppWebview {
965 webview_id,
966 label,
967 browser,
968 browser_id,
969 frame_navigation_state,
970 popup_family,
971 dialogs,
972 host,
973 uri_scheme_protocols,
974 devtools_protocol_handlers,
975 devtools_observer_registration,
976 devtools_enabled,
977 listeners: Default::default(),
978 bounds_rate,
979 offscreen,
980 })
981 .expect("failed to send initialized CEF browser");
982 }
983 });
984 let main_thread: request_handler::MainThreadDispatcher = Arc::new({
985 let context = context.clone();
986 move |task| {
987 let _ = context.send_message(Message::Task(task));
988 }
989 });
990 let request_context = request_context::request_context_from_webview_attributes(
991 &context.cache_path,
992 &pending.webview_attributes,
993 context.profile_preferences.clone(),
994 context.content_settings.clone(),
995 uri_scheme_protocols.keys(),
996 &custom_protocol_scheme,
997 scheme_registry.clone(),
998 main_thread,
999 on_initialized,
1000 );
1001 if request_context.is_none() {
1002 init_done.store(true, Ordering::SeqCst);
1003 }
1004 request_context::wait_for_deferred_init(&init_done);
1005
1006 browser_rx.recv().ok()
1010 }
1011
1012 pub(crate) fn handle_webview_message(
1013 &mut self,
1014 window_id: WindowId,
1015 webview_id: u32,
1016 message: WebviewMessage,
1017 ) {
1018 if self.state.exiting {
1020 return;
1021 }
1022
1023 match message {
1027 WebviewMessage::SetBounds(_)
1028 | WebviewMessage::SetSize(_)
1029 | WebviewMessage::SetPosition(_)
1030 | WebviewMessage::Position(_)
1031 | WebviewMessage::Size(_)
1032 | WebviewMessage::SetAutoResize(_)
1033 | WebviewMessage::WithWebview(_)
1034 | WebviewMessage::Reparent(_, _) => {
1035 self.handle_window_dependent_webview_message(window_id, webview_id, message);
1036 }
1037 message => {
1038 let Some(appwindow) = self.state.windows.get_mut(&window_id) else {
1039 return;
1040 };
1041 let Some(child) = appwindow
1042 .children
1043 .iter_mut()
1044 .find(|child| child.webview_id == webview_id)
1045 else {
1046 return;
1047 };
1048
1049 Self::handle_window_independent_webview_message(child, message);
1050 }
1051 }
1052 }
1053
1054 fn handle_window_dependent_webview_message(
1055 &mut self,
1056 window_id: WindowId,
1057 webview_id: u32,
1058 message: WebviewMessage,
1059 ) {
1060 let Some(appwindow) = self.state.windows.get_mut(&window_id) else {
1061 return;
1062 };
1063 let Some(child_index) = appwindow
1064 .children
1065 .iter_mut()
1066 .position(|child| child.webview_id == webview_id)
1067 else {
1068 return;
1069 };
1070
1071 match message {
1072 WebviewMessage::SetBounds(bounds) => {
1073 let parent_size = appwindow.safe_surface_size();
1074 let scale = appwindow.window.scale_factor();
1075 let child = &mut appwindow.children[child_index];
1076 child.set_bounds(parent_size, scale, bounds);
1077 }
1078 WebviewMessage::SetSize(size) => {
1079 let parent_size = appwindow.safe_surface_size();
1080 let scale = appwindow.window.scale_factor();
1081 let child = &mut appwindow.children[child_index];
1082 let bounds = child.bounds().unwrap_or_default();
1083 let new_bounds = Rect {
1084 position: bounds.position,
1085 size,
1086 };
1087 child.set_bounds(parent_size, scale, new_bounds);
1088 }
1089 WebviewMessage::SetPosition(position) => {
1090 let parent_size = appwindow.safe_surface_size();
1091 let scale = appwindow.window.scale_factor();
1092 let child = &mut appwindow.children[child_index];
1093 let bounds = child.bounds().unwrap_or_default();
1094 let new_bounds = Rect {
1095 position,
1096 size: bounds.size,
1097 };
1098 child.set_bounds(parent_size, scale, new_bounds);
1099 }
1100 WebviewMessage::Position(tx) => {
1101 let scale = appwindow.window.scale_factor();
1102 let child = &mut appwindow.children[child_index];
1103 let bounds = child.bounds().ok_or(Error::FailedToSendMessage);
1104 let position = bounds.map(|b| b.position);
1105 let position = position.map(|p| p.to_physical::<i32>(scale));
1106 let _ = tx.send(position);
1107 }
1108 WebviewMessage::Size(tx) => {
1109 let scale = appwindow.window.scale_factor();
1110 let child = &mut appwindow.children[child_index];
1111 let bounds = child.bounds().ok_or(Error::FailedToSendMessage);
1112 let size = bounds.map(|b| b.size.to_physical::<u32>(scale));
1113 let _ = tx.send(size);
1114 }
1115 WebviewMessage::SetAutoResize(auto_resize) => {
1116 if auto_resize {
1117 let parent_size = appwindow.safe_surface_size();
1118 let scale = appwindow.window.scale_factor();
1119 let child = &mut appwindow.children[child_index];
1120 let bounds = child.bounds();
1121 child.bounds_rate = compute_child_bounds_rate(bounds.as_ref(), true, parent_size, scale);
1122 } else {
1123 let child = &mut appwindow.children[child_index];
1124 child.bounds_rate = None;
1125 }
1126 }
1127 WebviewMessage::Reparent(target_window_id, tx) => {
1128 if appwindow.children[child_index].offscreen.is_some() && window_id != target_window_id {
1130 let _ = tx.send(Err(Error::CreateWebview(
1131 "offscreen webviews cannot be reparented".into(),
1132 )));
1133 return;
1134 }
1135 if window_id == target_window_id {
1136 let _ = tx.send(Ok(()));
1137 return;
1138 }
1139
1140 if !self.state.windows.contains_key(&target_window_id) {
1141 let _ = tx.send(Err(Error::WindowNotFound));
1142 return;
1143 }
1144
1145 let Some(mut child) = self
1146 .state
1147 .windows
1148 .get_mut(&window_id)
1149 .and_then(|appwindow| {
1150 appwindow
1151 .children
1152 .iter()
1153 .position(|child| child.webview_id == webview_id)
1154 .map(|index| appwindow.children.remove(index))
1155 })
1156 else {
1157 let _ = tx.send(Err(Error::WindowNotFound));
1158 return;
1159 };
1160
1161 let Some(target_appwindow) = self.state.windows.get_mut(&target_window_id) else {
1162 let _ = tx.send(Err(Error::WindowNotFound));
1163 return;
1164 };
1165
1166 let parent_size = target_appwindow.safe_surface_size();
1170 let bounds = child.bounds().unwrap_or_else(|| Rect {
1171 position: PhysicalPosition::new(0, 0).into(),
1172 size: parent_size.into(),
1173 });
1174 child.reparent(target_appwindow);
1175 child.set_bounds(parent_size, target_appwindow.window.scale_factor(), bounds);
1176 #[cfg(windows)]
1180 child.raise_to_top();
1181
1182 target_appwindow.children.push(child);
1183 let _ = tx.send(Ok(()));
1184 }
1185 WebviewMessage::WithWebview(callback) => {
1186 let child = &appwindow.children[child_index];
1187 let document = child
1188 .frame_navigation_state
1189 .observe_document(&child.browser);
1190 let dialogs = child.dialogs.snapshot(document.as_ref());
1191 let snapshot = WebviewSnapshot {
1192 browser_id: child.browser_id,
1193 dialogs,
1194 document,
1195 window_label: Some(appwindow.label.clone()),
1196 window: Some(appwindow.lifetime.clone()),
1197 parent_matches: if child.offscreen.is_some() {
1198 None
1199 } else {
1200 child.native_parent_matches(appwindow)
1201 },
1202 bounds: child.bounds(),
1203 visible: child
1204 .offscreen
1205 .as_ref()
1206 .map(|view| view.is_visible())
1207 .or_else(|| child.native_visible()),
1208 };
1209 let mut native = Webview::new(
1210 child.browser.clone(),
1211 snapshot,
1212 child.frame_navigation_state.clone(),
1213 );
1214 native.popups = child.popup_family.observe();
1215 callback(native);
1216 }
1217 _ => unreachable!("window-independent message routed to window-dependent handler"),
1218 }
1219 }
1220
1221 fn handle_window_independent_webview_message(child: &mut AppWebview, message: WebviewMessage) {
1222 match message {
1223 WebviewMessage::EvaluateScript(script) => {
1224 if let Some(frame) = child.browser.main_frame() {
1225 let script = cef::CefString::from(script.as_str());
1226 let url = cef::CefString::from("");
1227 frame.execute_java_script(Some(&script), Some(&url), 0);
1228 }
1229 }
1230 WebviewMessage::EvaluateScriptWithCallback(script, callback) => {
1231 let host = &child.host;
1232 let Ok(message_id) = crate::devtools::allocate_runtime_devtools_message_id() else {
1233 callback(String::new());
1234 return;
1235 };
1236 let callback = Arc::new(Mutex::new(Some(callback)));
1237 let registration = Arc::new(Mutex::new(None));
1238 let mut observer = EvalScriptWithCallbackDevToolsObserver::new(
1239 message_id,
1240 callback.clone(),
1241 registration.clone(),
1242 );
1243
1244 if let Some(observer_registration) =
1245 host.add_dev_tools_message_observer(Some(&mut observer))
1246 {
1247 *registration.lock().unwrap() = Some(observer_registration);
1248
1249 let message = serde_json::json!({
1250 "id": message_id,
1251 "method": "Runtime.evaluate",
1252 "params": {
1253 "expression": script,
1254 "returnByValue": true,
1255 }
1256 })
1257 .to_string();
1258
1259 if host.send_dev_tools_message(Some(message.as_bytes())) != 1 {
1260 let _ = registration.lock().unwrap().take();
1261 if let Some(callback) = callback.lock().unwrap().take() {
1262 callback(String::new());
1263 }
1264 }
1265 } else if let Some(callback) = callback.lock().unwrap().take() {
1266 callback(String::new());
1267 }
1268 }
1269 WebviewMessage::Navigate(url) => {
1270 if let Some(frame) = child.browser.main_frame() {
1271 frame.load_url(Some(&cef::CefString::from(url.as_str())));
1272 }
1273 }
1274 WebviewMessage::Reload => child.browser.reload(),
1275 WebviewMessage::GoBack => child.browser.go_back(),
1276 WebviewMessage::CanGoBack(tx) => _ = tx.send(Ok(child.browser.can_go_back() == 1)),
1277 WebviewMessage::GoForward => child.browser.go_forward(),
1278 WebviewMessage::CanGoForward(tx) => _ = tx.send(Ok(child.browser.can_go_forward() == 1)),
1279 WebviewMessage::Close => child.host.close_browser(0),
1280 WebviewMessage::SetFocus => child.host.set_focus(1),
1281 WebviewMessage::Url(tx) => {
1282 let url = child.url().unwrap_or_default();
1283 let _ = tx.send(Ok(url));
1284 }
1285 WebviewMessage::Bounds(tx) => {
1286 let bounds = child.bounds().ok_or(Error::FailedToSendMessage);
1287 let _ = tx.send(bounds);
1288 }
1289 WebviewMessage::WithWebview(_) => {
1290 unreachable!("window-dependent message routed to window-independent handler")
1291 }
1292 WebviewMessage::Print => child.host.print(),
1293 WebviewMessage::AddEventListener(event_id, handler) => {
1294 child.listeners.lock().unwrap().insert(event_id, handler);
1295 }
1296 WebviewMessage::Show => child.set_visible(true),
1297 WebviewMessage::Hide => child.set_visible(false),
1298 WebviewMessage::SetZoom(scale_factor) => {
1299 const CEF_ZOOM_BASE: f64 = 1.2;
1303 let zoom_level = if scale_factor > 0.0 {
1304 scale_factor.ln() / CEF_ZOOM_BASE.ln()
1305 } else {
1306 0.0
1307 };
1308 child.host.set_zoom_level(zoom_level);
1309 }
1310 WebviewMessage::SetBackgroundColor(color) => child.set_background_color(color),
1311 WebviewMessage::ClearAllBrowsingData => {
1312 if let Some(manager) = child.cookie_manager() {
1313 manager.delete_cookies(None, None, None);
1314 manager.flush_store(None);
1315 }
1316 if let Some(request_context) = child.host.request_context() {
1317 request_context.clear_http_cache(None);
1318 }
1319 }
1320 WebviewMessage::CookiesForUrl(url, tx) => {
1321 if let Some(manager) = child.cookie_manager() {
1322 cookie::visit_url_cookies(manager, url, tx);
1323 } else {
1324 let _ = tx.send(Ok(Vec::new()));
1325 }
1326 }
1327 WebviewMessage::Cookies(tx) => {
1328 if let Some(manager) = child.cookie_manager() {
1329 cookie::visit_all_cookies(manager, tx);
1330 } else {
1331 let _ = tx.send(Ok(Vec::new()));
1332 }
1333 }
1334 WebviewMessage::SetCookie(cookie) => {
1335 if let Some(manager) = child.cookie_manager() {
1336 let url = child.url();
1337 cookie::set_cookie(manager, url, cookie);
1338 }
1339 }
1340 WebviewMessage::DeleteCookie(cookie) => {
1341 if let Some(manager) = child.cookie_manager() {
1342 let url = child.url();
1343 cookie::delete_cookie(manager, url, cookie);
1344 }
1345 }
1346 #[cfg(any(debug_assertions, feature = "devtools"))]
1350 WebviewMessage::OpenDevTools => {
1351 if child.devtools_enabled {
1352 child.host.show_dev_tools(None, None, None, None);
1353 } else {
1354 log::warn!(
1355 "not opening devtools for webview {:?}: they are disabled for this webview \
1356 or by Cef::devtools",
1357 child.label
1358 );
1359 }
1360 }
1361 #[cfg(any(debug_assertions, feature = "devtools"))]
1362 WebviewMessage::CloseDevTools => child.host.close_dev_tools(),
1363 #[cfg(any(debug_assertions, feature = "devtools"))]
1364 WebviewMessage::IsDevToolsOpen(tx) => _ = tx.send(child.host.has_dev_tools() == 1),
1365 WebviewMessage::SendDevToolsMessage(message, tx) => {
1366 let result = child.host.send_dev_tools_message(Some(&message));
1367 let _ = tx.send(if result == 1 {
1368 Ok(())
1369 } else {
1370 Err(Error::FailedToSendMessage)
1371 });
1372 }
1373 WebviewMessage::OnDevToolsProtocol(handler, tx) => {
1374 child
1375 .devtools_protocol_handlers
1376 .lock()
1377 .unwrap()
1378 .push(handler);
1379
1380 let needs_devtools_observer = child
1381 .devtools_observer_registration
1382 .lock()
1383 .unwrap()
1384 .is_none();
1385 if needs_devtools_observer {
1386 if let Some(registration) = add_dev_tools_observer(
1387 &child.browser,
1388 child.devtools_protocol_handlers.clone(),
1389 Arc::new(Mutex::new(HashMap::new())),
1390 child.dialogs.clone(),
1391 ) {
1392 *child.devtools_observer_registration.lock().unwrap() = Some(registration);
1393 let _ = tx.send(Ok(()));
1394 } else {
1395 let _ = tx.send(Err(Error::FailedToSendMessage));
1396 }
1397 } else {
1398 let _ = tx.send(Ok(()));
1399 }
1400 }
1401 WebviewMessage::SetBounds(_)
1402 | WebviewMessage::SetSize(_)
1403 | WebviewMessage::SetPosition(_)
1404 | WebviewMessage::Position(_)
1405 | WebviewMessage::Size(_)
1406 | WebviewMessage::SetAutoResize(_)
1407 | WebviewMessage::Reparent(_, _) => {
1408 unreachable!("window-dependent message routed to window-independent handler")
1409 }
1410 }
1411 }
1412}
1413
1414#[derive(Clone, Copy, Debug)]
1415pub enum RuntimeStyle {
1416 Alloy,
1417 Chrome,
1418}
1419
1420#[derive(Default, Clone)]
1498pub struct CefWebviewAttributes {
1499 pub offscreen: Option<crate::Offscreen>,
1501 pub runtime_style: Option<RuntimeStyle>,
1503 pub frame_event_handler: Option<Arc<crate::FrameEventHandler>>,
1514 pub console_message_handler: Option<Arc<ConsoleMessageHandler>>,
1519 pub allowed_chrome_commands: Vec<ChromeCommandGroup>,
1523 pub browser_settings_callback: Option<Arc<BrowserSettingsCallback>>,
1530}
1531
1532impl std::fmt::Debug for CefWebviewAttributes {
1533 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1534 formatter
1535 .debug_struct("CefWebviewAttributes")
1536 .field("runtime_style", &self.runtime_style)
1537 .field("offscreen", &self.offscreen.is_some())
1538 .field("frame_event_handler", &self.frame_event_handler.is_some())
1539 .field(
1540 "console_message_handler",
1541 &self.console_message_handler.is_some(),
1542 )
1543 .field("allowed_chrome_commands", &self.allowed_chrome_commands)
1544 .field(
1545 "browser_settings_callback",
1546 &self.browser_settings_callback.is_some(),
1547 )
1548 .finish()
1549 }
1550}
1551
1552#[derive(Debug, Clone)]
1553pub struct CefInitScript {
1554 pub(crate) script: String,
1555 pub(crate) hash: String,
1556 for_main_frame_only: bool,
1557}
1558
1559impl CefInitScript {
1560 pub(crate) fn runs_in_frame(&self, is_main_frame: bool) -> bool {
1562 is_main_frame || !self.for_main_frame_only
1563 }
1564
1565 fn new(script: InitializationScript) -> Self {
1566 let mut hasher = Sha256::new();
1567 hasher.update(normalize_script_for_csp(script.script.as_bytes()));
1568 let hash = format!(
1569 "'sha256-{}'",
1570 base64::Engine::encode(
1571 &base64::engine::general_purpose::STANDARD,
1572 hasher.finalize()
1573 )
1574 );
1575 Self {
1576 script: script.script,
1577 hash,
1578 for_main_frame_only: script.for_main_frame_only,
1579 }
1580 }
1581}
1582
1583#[cfg(any(debug_assertions, feature = "devtools"))]
1591fn is_alloy_style(runtime_style: Option<RuntimeStyle>) -> bool {
1592 cfg!(target_os = "macos") || matches!(runtime_style, Some(RuntimeStyle::Alloy))
1593}
1594
1595pub(crate) fn initialization_scripts(attrs: &mut WebviewAttributes) -> Arc<Vec<CefInitScript>> {
1596 let mut initialization_scripts = Vec::new();
1597
1598 if attrs.drag_drop_handler_enabled {
1599 let drag_script = browser_client::drag_drop_initialization_script();
1600 initialization_scripts.push(CefInitScript::new(drag_script));
1601 }
1602
1603 initialization_scripts.extend(
1604 std::mem::take(&mut attrs.initialization_scripts)
1605 .into_iter()
1606 .map(CefInitScript::new),
1607 );
1608
1609 Arc::new(initialization_scripts)
1610}
1611
1612#[derive(Debug, Clone)]
1613pub struct CefWebviewDispatcher<T: UserEvent> {
1614 pub(crate) window_id: Arc<Mutex<WindowId>>,
1615 pub(crate) webview_id: u32,
1616 pub(crate) context: RuntimeContext<T>,
1617}
1618
1619impl<T: UserEvent> CefWebviewDispatcher<T> {
1620 pub fn send_dev_tools_message(&self, message: &[u8]) -> Result<()> {
1630 let (tx, rx) = mpsc::channel();
1631 self.context.send_message(Message::Webview {
1632 window_id: *self.window_id.lock().unwrap(),
1633 webview_id: self.webview_id,
1634 message: WebviewMessage::SendDevToolsMessage(message.to_vec(), tx),
1635 })?;
1636 rx.recv().map_err(|_| Error::FailedToReceiveMessage)?
1637 }
1638
1639 pub fn on_dev_tools_protocol<F: Fn(DevToolsProtocol) + Send + Sync + 'static>(
1653 &self,
1654 f: F,
1655 ) -> Result<()> {
1656 let (tx, rx) = mpsc::channel();
1657 let handler =
1658 Arc::new(move |protocol: DevToolsProtocol| f(protocol)) as Arc<DevToolsProtocolHandler>;
1659 self.context.send_message(Message::Webview {
1660 window_id: *self.window_id.lock().unwrap(),
1661 webview_id: self.webview_id,
1662 message: WebviewMessage::OnDevToolsProtocol(handler, tx),
1663 })?;
1664 rx.recv().map_err(|_| Error::FailedToReceiveMessage)?
1665 }
1666}
1667
1668pub(crate) fn create_webview_detached<T: UserEvent>(
1669 context: &RuntimeContext<T>,
1670 window_id: WindowId,
1671 pending: PendingWebview<T, CefRuntime<T>>,
1672) -> Result<DetachedWebview<T, CefRuntime<T>>> {
1673 let label = pending.label.clone();
1674 let webview_id = context.next_webview_id();
1675 let (result_tx, result_rx) = mpsc::channel();
1676 context.send_message(Message::CreateWebview {
1677 window_id,
1678 webview_id,
1679 pending: Box::new(pending),
1680 result_tx,
1681 })?;
1682 context.wait_for_creation(result_rx)?;
1685 Ok(DetachedWebview {
1686 label,
1687 dispatcher: CefWebviewDispatcher {
1688 window_id: Arc::new(Mutex::new(window_id)),
1689 webview_id,
1690 context: context.clone(),
1691 },
1692 })
1693}
1694
1695fn getter<T: UserEvent, R>(
1696 context: &RuntimeContext<T>,
1697 message: Message<T>,
1698 receiver: Receiver<Result<R>>,
1699) -> Result<R> {
1700 context.send_message(message)?;
1701 receiver.recv().map_err(|_| Error::FailedToReceiveMessage)?
1702}
1703
1704macro_rules! webview_getter {
1705 ($self:ident, $variant:ident) => {{
1706 let (tx, rx) = mpsc::channel();
1707 let window_id = *$self.window_id.lock().unwrap();
1710 getter(
1711 &$self.context,
1712 Message::Webview {
1713 window_id,
1714 webview_id: $self.webview_id,
1715 message: WebviewMessage::$variant(tx),
1716 },
1717 rx,
1718 )
1719 }};
1720}
1721
1722#[cfg(test)]
1723mod getter_tests {
1724 use super::{Message, WebviewMessage};
1725 use std::sync::{Arc, Mutex, mpsc};
1726 use tauri_runtime::{Result, window::WindowId};
1727
1728 struct Dispatcher {
1732 context: Arc<Mutex<WindowId>>,
1733 window_id: Arc<Mutex<WindowId>>,
1734 webview_id: u32,
1735 }
1736
1737 fn getter(
1738 window_id: &Arc<Mutex<WindowId>>,
1739 message: Message<()>,
1740 receiver: mpsc::Receiver<Result<String>>,
1741 ) -> Result<String> {
1742 let Message::Webview {
1743 window_id: requested_window,
1744 message: WebviewMessage::Url(reply),
1745 ..
1746 } = message
1747 else {
1748 panic!("expected a URL request");
1749 };
1750 let callback_window = window_id
1751 .try_lock()
1752 .expect("the UI callback must acquire the window lock before replying");
1753 assert_eq!(*callback_window, requested_window);
1754 reply.send(Ok("https://example.test/".into())).unwrap();
1755 receiver.recv().unwrap()
1756 }
1757
1758 #[test]
1759 fn url_getter_does_not_hold_the_window_lock_while_waiting_for_ui() {
1760 let window_id = Arc::new(Mutex::new(WindowId::from(1)));
1761 let dispatcher = Dispatcher {
1762 context: Arc::clone(&window_id),
1763 window_id,
1764 webview_id: 1,
1765 };
1766 assert_eq!(
1767 webview_getter!(dispatcher, Url).unwrap(),
1768 "https://example.test/"
1769 );
1770 }
1771}
1772
1773impl<T: UserEvent> WebviewDispatch<T> for CefWebviewDispatcher<T> {
1774 type Runtime = CefRuntime<T>;
1775
1776 fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {
1777 self.context.run_on_main_thread(f)
1778 }
1779
1780 fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) -> WebviewEventId {
1781 let id = self.context.next_webview_event_id();
1782 let _ = self.context.send_message(Message::Webview {
1783 window_id: *self.window_id.lock().unwrap(),
1784 webview_id: self.webview_id,
1785 message: WebviewMessage::AddEventListener(id, Box::new(f)),
1786 });
1787 id
1788 }
1789
1790 fn with_webview<F: FnOnce(<Self::Runtime as Runtime<T>>::Webview) + Send + 'static>(
1791 &self,
1792 f: F,
1793 ) -> Result<()> {
1794 self.context.send_message(Message::Webview {
1795 window_id: *self.window_id.lock().unwrap(),
1796 webview_id: self.webview_id,
1797 message: WebviewMessage::WithWebview(Box::new(f)),
1798 })
1799 }
1800
1801 fn open_devtools(&self) {
1802 #[cfg(any(debug_assertions, feature = "devtools"))]
1803 {
1804 let _ = self.context.send_message(Message::Webview {
1805 window_id: *self.window_id.lock().unwrap(),
1806 webview_id: self.webview_id,
1807 message: WebviewMessage::OpenDevTools,
1808 });
1809 }
1810 #[cfg(not(any(debug_assertions, feature = "devtools")))]
1811 log::warn!("devtools are not available: enable the `devtools` feature of `tauri-runtime-cef`");
1812 }
1813
1814 fn close_devtools(&self) {
1815 #[cfg(any(debug_assertions, feature = "devtools"))]
1816 {
1817 let _ = self.context.send_message(Message::Webview {
1818 window_id: *self.window_id.lock().unwrap(),
1819 webview_id: self.webview_id,
1820 message: WebviewMessage::CloseDevTools,
1821 });
1822 }
1823 }
1824
1825 fn is_devtools_open(&self) -> Result<bool> {
1826 #[cfg(any(debug_assertions, feature = "devtools"))]
1827 {
1828 let (tx, rx) = mpsc::channel();
1829 self.context.send_message(Message::Webview {
1830 window_id: *self.window_id.lock().unwrap(),
1831 webview_id: self.webview_id,
1832 message: WebviewMessage::IsDevToolsOpen(tx),
1833 })?;
1834 rx.recv().map_err(|_| Error::FailedToReceiveMessage)
1835 }
1836 #[cfg(not(any(debug_assertions, feature = "devtools")))]
1837 {
1838 Ok(false)
1839 }
1840 }
1841
1842 fn url(&self) -> Result<String> {
1843 webview_getter!(self, Url)
1844 }
1845
1846 fn bounds(&self) -> Result<Rect> {
1847 webview_getter!(self, Bounds)
1848 }
1849
1850 fn position(&self) -> Result<PhysicalPosition<i32>> {
1851 webview_getter!(self, Position)
1852 }
1853
1854 fn size(&self) -> Result<PhysicalSize<u32>> {
1855 webview_getter!(self, Size)
1856 }
1857
1858 fn navigate(&self, url: Url) -> Result<()> {
1859 self.context.send_message(Message::Webview {
1860 window_id: *self.window_id.lock().unwrap(),
1861 webview_id: self.webview_id,
1862 message: WebviewMessage::Navigate(url),
1863 })
1864 }
1865
1866 fn reload(&self) -> Result<()> {
1867 self.context.send_message(Message::Webview {
1868 window_id: *self.window_id.lock().unwrap(),
1869 webview_id: self.webview_id,
1870 message: WebviewMessage::Reload,
1871 })
1872 }
1873
1874 fn go_back(&self) -> Result<()> {
1875 self.context.send_message(Message::Webview {
1876 window_id: *self.window_id.lock().unwrap(),
1877 webview_id: self.webview_id,
1878 message: WebviewMessage::GoBack,
1879 })
1880 }
1881
1882 fn can_go_back(&self) -> Result<bool> {
1883 webview_getter!(self, CanGoBack)
1884 }
1885
1886 fn go_forward(&self) -> Result<()> {
1887 self.context.send_message(Message::Webview {
1888 window_id: *self.window_id.lock().unwrap(),
1889 webview_id: self.webview_id,
1890 message: WebviewMessage::GoForward,
1891 })
1892 }
1893
1894 fn can_go_forward(&self) -> Result<bool> {
1895 webview_getter!(self, CanGoForward)
1896 }
1897
1898 fn print(&self) -> Result<()> {
1899 self.context.send_message(Message::Webview {
1900 window_id: *self.window_id.lock().unwrap(),
1901 webview_id: self.webview_id,
1902 message: WebviewMessage::Print,
1903 })
1904 }
1905
1906 fn close(&self) -> Result<()> {
1907 self.context.send_message(Message::Webview {
1908 window_id: *self.window_id.lock().unwrap(),
1909 webview_id: self.webview_id,
1910 message: WebviewMessage::Close,
1911 })
1912 }
1913
1914 fn set_bounds(&self, bounds: Rect) -> Result<()> {
1915 self.context.send_message(Message::Webview {
1916 window_id: *self.window_id.lock().unwrap(),
1917 webview_id: self.webview_id,
1918 message: WebviewMessage::SetBounds(bounds),
1919 })
1920 }
1921
1922 fn set_size(&self, size: Size) -> Result<()> {
1923 self.context.send_message(Message::Webview {
1924 window_id: *self.window_id.lock().unwrap(),
1925 webview_id: self.webview_id,
1926 message: WebviewMessage::SetSize(size),
1927 })
1928 }
1929
1930 fn set_position(&self, position: Position) -> Result<()> {
1931 self.context.send_message(Message::Webview {
1932 window_id: *self.window_id.lock().unwrap(),
1933 webview_id: self.webview_id,
1934 message: WebviewMessage::SetPosition(position),
1935 })
1936 }
1937
1938 fn set_focus(&self) -> Result<()> {
1939 self.context.send_message(Message::Webview {
1940 window_id: *self.window_id.lock().unwrap(),
1941 webview_id: self.webview_id,
1942 message: WebviewMessage::SetFocus,
1943 })
1944 }
1945
1946 fn hide(&self) -> Result<()> {
1947 self.context.send_message(Message::Webview {
1948 window_id: *self.window_id.lock().unwrap(),
1949 webview_id: self.webview_id,
1950 message: WebviewMessage::Hide,
1951 })
1952 }
1953
1954 fn show(&self) -> Result<()> {
1955 self.context.send_message(Message::Webview {
1956 window_id: *self.window_id.lock().unwrap(),
1957 webview_id: self.webview_id,
1958 message: WebviewMessage::Show,
1959 })
1960 }
1961
1962 fn eval_script<S: Into<String>>(&self, script: S) -> Result<()> {
1963 self.context.send_message(Message::Webview {
1964 window_id: *self.window_id.lock().unwrap(),
1965 webview_id: self.webview_id,
1966 message: WebviewMessage::EvaluateScript(script.into()),
1967 })
1968 }
1969
1970 fn eval_script_with_callback<S: Into<String>>(
1971 &self,
1972 script: S,
1973 callback: impl Fn(String) + Send + 'static,
1974 ) -> Result<()> {
1975 self.context.send_message(Message::Webview {
1976 window_id: *self.window_id.lock().unwrap(),
1977 webview_id: self.webview_id,
1978 message: WebviewMessage::EvaluateScriptWithCallback(script.into(), Box::new(callback)),
1979 })
1980 }
1981
1982 fn reparent(&self, window_id: WindowId) -> Result<()> {
1983 let (tx, rx) = mpsc::channel();
1984 self.context.send_message(Message::Webview {
1985 window_id: *self.window_id.lock().unwrap(),
1986 webview_id: self.webview_id,
1987 message: WebviewMessage::Reparent(window_id, tx),
1988 })?;
1989 let result = rx.recv().map_err(|_| Error::FailedToReceiveMessage)?;
1990 if result.is_ok() {
1991 *self.window_id.lock().unwrap() = window_id;
1992 }
1993 result
1994 }
1995
1996 fn cookies_for_url(&self, url: Url) -> Result<Vec<Cookie<'static>>> {
1997 let (tx, rx) = mpsc::channel();
1998 self.context.send_message(Message::Webview {
1999 window_id: *self.window_id.lock().unwrap(),
2000 webview_id: self.webview_id,
2001 message: WebviewMessage::CookiesForUrl(url, tx),
2002 })?;
2003 rx.recv().map_err(|_| Error::FailedToReceiveMessage)?
2004 }
2005
2006 fn cookies(&self) -> Result<Vec<Cookie<'static>>> {
2007 webview_getter!(self, Cookies)
2008 }
2009
2010 fn set_cookie(&self, cookie: Cookie<'_>) -> Result<()> {
2011 self.context.send_message(Message::Webview {
2012 window_id: *self.window_id.lock().unwrap(),
2013 webview_id: self.webview_id,
2014 message: WebviewMessage::SetCookie(cookie.into_owned()),
2015 })
2016 }
2017
2018 fn delete_cookie(&self, cookie: Cookie<'_>) -> Result<()> {
2019 self.context.send_message(Message::Webview {
2020 window_id: *self.window_id.lock().unwrap(),
2021 webview_id: self.webview_id,
2022 message: WebviewMessage::DeleteCookie(cookie.into_owned()),
2023 })
2024 }
2025
2026 fn set_auto_resize(&self, auto_resize: bool) -> Result<()> {
2027 self.context.send_message(Message::Webview {
2028 window_id: *self.window_id.lock().unwrap(),
2029 webview_id: self.webview_id,
2030 message: WebviewMessage::SetAutoResize(auto_resize),
2031 })
2032 }
2033
2034 fn set_zoom(&self, scale_factor: f64) -> Result<()> {
2035 self.context.send_message(Message::Webview {
2036 window_id: *self.window_id.lock().unwrap(),
2037 webview_id: self.webview_id,
2038 message: WebviewMessage::SetZoom(scale_factor),
2039 })
2040 }
2041
2042 fn set_background_color(&self, color: Option<Color>) -> Result<()> {
2043 self.context.send_message(Message::Webview {
2044 window_id: *self.window_id.lock().unwrap(),
2045 webview_id: self.webview_id,
2046 message: WebviewMessage::SetBackgroundColor(color),
2047 })
2048 }
2049
2050 fn clear_all_browsing_data(&self) -> Result<()> {
2051 self.context.send_message(Message::Webview {
2052 window_id: *self.window_id.lock().unwrap(),
2053 webview_id: self.webview_id,
2054 message: WebviewMessage::ClearAllBrowsingData,
2055 })
2056 }
2057}
2058
2059pub(crate) fn layout_app_window(appwindow: &AppWindow) {
2065 #[cfg(target_os = "linux")]
2069 appwindow.cef_host.set_visible(
2070 appwindow.children.is_empty()
2071 || appwindow
2072 .children
2073 .iter()
2074 .any(|child| child.offscreen.is_none()),
2075 );
2076 let parent_size = appwindow.safe_surface_size();
2077 let win_w = parent_size.width as f32;
2078 let win_h = parent_size.height as f32;
2079 let scale = appwindow.window.scale_factor();
2080 for child in &appwindow.children {
2081 let Some(rate) = child.bounds_rate else {
2082 continue;
2083 };
2084 let x = (rate.x * win_w).round() as i32;
2085 let y = (rate.y * win_h).round() as i32;
2086 let w = (rate.width * win_w).round() as i32;
2087 let h = (rate.height * win_h).round() as i32;
2088 child.host.notify_move_or_resize_started();
2089 child.resize(scale, x, y, w, h);
2090 child.host.was_resized();
2091 }
2092}
2093
2094pub(crate) fn compute_child_bounds_rate(
2099 bounds: Option<&Rect>,
2100 auto_resize: bool,
2101 parent_size: PhysicalSize<u32>,
2102 scale: f64,
2103) -> Option<BoundsRate> {
2104 let Some(bounds) = bounds else {
2105 return Some(BoundsRate::default());
2106 };
2107
2108 if !auto_resize {
2109 return None;
2110 }
2111
2112 let min_w = parent_size.width.max(1) as i32;
2113 let min_h = parent_size.height.max(1) as i32;
2114
2115 let pos = bounds.position.to_physical::<i32>(scale);
2116 let size = bounds.size.to_physical::<u32>(scale);
2117
2118 let x = pos.x;
2119 let y = pos.y;
2120 let w = size.width;
2121 let h = size.height;
2122
2123 Some(BoundsRate {
2124 x: x as f32 / min_w as f32,
2125 y: y as f32 / min_h as f32,
2126 width: w as f32 / min_w as f32,
2127 height: h as f32 / min_h as f32,
2128 })
2129}
2130
2131pub(crate) const INITIAL_LOAD_URL: &str = concat!(
2132 "data:text/html;charset=utf-8,",
2133 "%3C!doctype%20html%3E",
2134 "%3Chtml%20data-tauri-cef-internal%3D%22initial-load%22%3E",
2135 "%3Chead%3E",
2136 "%3Cmeta%20charset%3D%22utf-8%22%3E",
2137 "%3Ctitle%3ETauri%20CEF%20Initial%20Load%3C%2Ftitle%3E",
2138 "%3C%2Fhead%3E",
2139 "%3Cbody%20data-tauri-cef-internal%3D%22initial-load%22%3E",
2140 "%3C!--%20Tauri%20CEF%20internal%20initial%20load%20placeholder%20--%3E",
2141 "%3C%2Fbody%3E",
2142 "%3C%2Fhtml%3E",
2143);
2144
2145pub(crate) type PendingInitialLoads = Arc<Mutex<HashMap<i32, (Browser, String)>>>;
2153
2154cef::wrap_dev_tools_message_observer! {
2155 struct TauriDevToolsProtocolObserver {
2156 handlers: Arc<Mutex<Vec<Arc<DevToolsProtocolHandler>>>>,
2157 pending_initial_loads: PendingInitialLoads,
2158 dialogs: crate::dialog::DialogState,
2159 }
2160
2161 impl DevToolsMessageObserver {
2162 fn on_dev_tools_message(
2163 &self,
2164 _browser: Option<&mut cef::Browser>,
2165 message: Option<&[u8]>,
2166 ) -> std::os::raw::c_int {
2167 if let Some(message) = message {
2168 let protocol = DevToolsProtocol::Message(message.to_vec());
2169 if let Ok(handlers) = self.handlers.lock() {
2170 for handler in handlers.iter() {
2171 handler(protocol.clone());
2172 }
2173 }
2174 }
2175 0
2176 }
2177
2178 fn on_dev_tools_method_result(
2179 &self,
2180 _browser: Option<&mut Browser>,
2181 message_id: std::os::raw::c_int,
2182 success: std::os::raw::c_int,
2183 result: Option<&[u8]>,
2184 ) {
2185 if let Some((browser, initial_url)) = self
2188 .pending_initial_loads
2189 .lock()
2190 .unwrap()
2191 .remove(&message_id)
2192 {
2193 post_load_initial_url(browser, initial_url);
2194 }
2195
2196 let protocol = DevToolsProtocol::MethodResult {
2197 message_id,
2198 success: success != 0,
2199 result: result.map(|r| r.to_vec()).unwrap_or_default(),
2200 };
2201 if let Ok(handlers) = self.handlers.lock() {
2202 for handler in handlers.iter() {
2203 handler(protocol.clone());
2204 }
2205 }
2206 }
2207
2208 fn on_dev_tools_event(
2209 &self,
2210 browser: Option<&mut Browser>,
2211 method: Option<&CefString>,
2212 params: Option<&[u8]>,
2213 ) {
2214 if let (Some(browser), Some(method)) = (browser, method)
2215 && self.dialogs.accepts_browser(browser.identifier()) {
2216 self.dialogs.on_event(&method.to_string(), params.unwrap_or_default());
2217 }
2218 let protocol = DevToolsProtocol::Event {
2219 method: method.map(|m| format!("{m}")).unwrap_or_default(),
2220 params: params.map(|p| p.to_vec()).unwrap_or_default(),
2221 };
2222 if let Ok(handlers) = self.handlers.lock() {
2223 for handler in handlers.iter() {
2224 handler(protocol.clone());
2225 }
2226 }
2227 }
2228 }
2229}
2230
2231fn runtime_evaluate_result_to_json(result: Option<&[u8]>) -> String {
2232 let Some(result) = result else {
2233 return String::new();
2234 };
2235 let Ok(result) = serde_json::from_slice::<serde_json::Value>(result) else {
2236 return String::new();
2237 };
2238
2239 if result.get("exceptionDetails").is_some() {
2240 return String::new();
2241 }
2242
2243 let remote_object = result.get("result").unwrap_or(&result);
2244 remote_object
2245 .get("value")
2246 .and_then(|value| serde_json::to_string(value).ok())
2247 .unwrap_or_default()
2248}
2249
2250type EvalScriptCallback = Box<dyn Fn(String) + Send + 'static>;
2251
2252cef::wrap_dev_tools_message_observer! {
2253 struct EvalScriptWithCallbackDevToolsObserver {
2254 message_id: i32,
2255 callback: Arc<Mutex<Option<EvalScriptCallback>>>,
2256 registration: Arc<Mutex<Option<cef::Registration>>>,
2257 }
2258
2259 impl DevToolsMessageObserver {
2260 fn on_dev_tools_method_result(
2261 &self,
2262 _browser: Option<&mut Browser>,
2263 message_id: std::os::raw::c_int,
2264 success: std::os::raw::c_int,
2265 result: Option<&[u8]>,
2266 ) {
2267 if message_id != self.message_id {
2268 return;
2269 }
2270
2271 let Some(callback) = self.callback.lock().unwrap().take() else {
2272 return;
2273 };
2274
2275 let result = if success != 0 {
2276 runtime_evaluate_result_to_json(result)
2277 } else {
2278 String::new()
2279 };
2280 callback(result);
2281
2282 let _ = self.registration.lock().unwrap().take();
2283 }
2284 }
2285}
2286
2287fn apply_user_agent_override(host: &BrowserHost, user_agent: &str) {
2304 let Ok(message_id) = crate::devtools::allocate_runtime_devtools_message_id() else {
2305 log::warn!("could not set the webview user agent: no DevTools message id was available");
2306 return;
2307 };
2308
2309 let message = serde_json::json!({
2310 "id": message_id,
2311 "method": "Emulation.setUserAgentOverride",
2312 "params": { "userAgent": user_agent },
2313 })
2314 .to_string();
2315
2316 if host.send_dev_tools_message(Some(message.as_bytes())) != 1 {
2317 log::warn!("failed to set the webview user agent through the DevTools protocol");
2318 }
2319}
2320
2321pub(crate) fn add_dev_tools_observer(
2322 browser: &Browser,
2323 handlers: Arc<Mutex<Vec<Arc<DevToolsProtocolHandler>>>>,
2324 pending_initial_loads: PendingInitialLoads,
2325 dialogs: crate::dialog::DialogState,
2326) -> Option<cef::Registration> {
2327 browser.host().and_then(|host| {
2328 let mut observer = TauriDevToolsProtocolObserver::new(handlers, pending_initial_loads, dialogs);
2329 let registration = host.add_dev_tools_message_observer(Some(&mut observer))?;
2330 if let Ok(id) = crate::devtools::allocate_runtime_devtools_message_id() {
2331 let message = serde_json::json!({"id":id,"method":"Page.enable","params":{}}).to_string();
2332 let _ = host.send_dev_tools_message(Some(message.as_bytes()));
2333 }
2334 Some(registration)
2335 })
2336}
2337
2338fn devtools_initialization_script_source(
2339 initialization_scripts: &[CefInitScript],
2340 custom_protocol_scheme: &str,
2341 custom_scheme_domain_names: &[String],
2342) -> Option<String> {
2343 if initialization_scripts.is_empty() {
2344 return None;
2345 }
2346
2347 let custom_protocol = serde_json::to_string(&format!("{custom_protocol_scheme}:")).ok()?;
2348 let custom_domains = serde_json::to_string(custom_scheme_domain_names).ok()?;
2349 let mut source = format!(
2350 r#"{{
2351 const __TAURI_CEF_INIT_CUSTOM_PROTOCOL__ = {custom_protocol};
2352 const __TAURI_CEF_INIT_CUSTOM_DOMAINS__ = new Set({custom_domains});
2353 const __TAURI_CEF_INIT_IS_CUSTOM_PROTOCOL__ =
2354 location.protocol === __TAURI_CEF_INIT_CUSTOM_PROTOCOL__
2355 && __TAURI_CEF_INIT_CUSTOM_DOMAINS__.has(location.hostname);
2356 const __TAURI_CEF_INIT_IS_MAIN_FRAME__ = (() => {{
2357 try {{
2358 return window.top === window;
2359 }} catch (_) {{
2360 return false;
2361 }}
2362 }})();
2363"#
2364 );
2365
2366 for init_script in initialization_scripts {
2367 source.push_str(" if (!__TAURI_CEF_INIT_IS_CUSTOM_PROTOCOL__");
2368 if init_script.for_main_frame_only {
2369 source.push_str(" && __TAURI_CEF_INIT_IS_MAIN_FRAME__");
2370 }
2371 source.push_str(") {\n");
2372 source.push_str(init_script.script.as_str());
2373 source.push_str("\n }\n");
2374 }
2375
2376 source.push_str("}\n");
2377 Some(source)
2378}
2379
2380fn register_initialization_scripts(
2381 browser: &Browser,
2382 initialization_scripts: &[CefInitScript],
2383 custom_protocol_scheme: &str,
2384 custom_scheme_domain_names: &[String],
2385 initial_url: String,
2386 pending_initial_loads: &PendingInitialLoads,
2387) -> std::result::Result<bool, crate::DevToolsMessageIdExhausted> {
2388 let Some(source) = devtools_initialization_script_source(
2389 initialization_scripts,
2390 custom_protocol_scheme,
2391 custom_scheme_domain_names,
2392 ) else {
2393 return Ok(false);
2394 };
2395 let Some(host) = browser.host() else {
2396 return Ok(false);
2397 };
2398
2399 let message_id = crate::devtools::allocate_runtime_devtools_message_id()?;
2400 let message = serde_json::json!({
2401 "id": message_id,
2402 "method": "Page.addScriptToEvaluateOnNewDocument",
2403 "params": {
2404 "source": source,
2405 }
2406 })
2407 .to_string();
2408
2409 pending_initial_loads
2410 .lock()
2411 .unwrap()
2412 .insert(message_id, (browser.clone(), initial_url));
2413 if host.send_dev_tools_message(Some(message.as_bytes())) == 1 {
2414 Ok(true)
2415 } else {
2416 pending_initial_loads.lock().unwrap().remove(&message_id);
2417 Ok(false)
2418 }
2419}
2420
2421wrap_task! {
2422 struct LoadInitialUrlTask {
2423 browser: Browser,
2424 initial_url: String,
2425 }
2426
2427 impl Task {
2428 fn execute(&self) {
2429 load_initial_url(&self.browser, &self.initial_url);
2430 }
2431 }
2432}
2433
2434fn post_load_initial_url(browser: Browser, initial_url: String) {
2435 let mut task = LoadInitialUrlTask::new(browser, initial_url);
2436 cef::post_task(sys::cef_thread_id_t::TID_UI.into(), Some(&mut task));
2437}
2438
2439pub(crate) fn load_initial_url_after_registering_initialization_scripts(
2448 browser: &Browser,
2449 initialization_scripts: &[CefInitScript],
2450 custom_protocol_scheme: &str,
2451 custom_scheme_domain_names: &[String],
2452 initial_url: &str,
2453 pending_initial_loads: &PendingInitialLoads,
2454) {
2455 let browser_for_callback = browser.clone();
2456 let initial_url = initial_url.to_string();
2457 let is_waiting_for_initialization_scripts = register_initialization_scripts(
2458 browser,
2459 initialization_scripts,
2460 custom_protocol_scheme,
2461 custom_scheme_domain_names,
2462 initial_url.clone(),
2463 pending_initial_loads,
2464 );
2465
2466 match is_waiting_for_initialization_scripts {
2467 Ok(false) => post_load_initial_url(browser_for_callback, initial_url),
2468 Ok(true) => {}
2469 Err(error) => {
2470 log::error!("CEF initialization failed: {error}");
2473 if let Some(host) = browser.host() {
2474 host.close_browser(1);
2475 }
2476 }
2477 }
2478}
2479
2480fn load_initial_url(browser: &Browser, initial_url: &str) {
2481 if let Some(frame) = browser.main_frame() {
2482 frame.load_url(Some(&CefString::from(initial_url)));
2483 }
2484}
2485
2486#[cfg(test)]
2487mod tests {
2488 use super::*;
2489
2490 #[test]
2491 fn offscreen_background_is_transparent_only_on_request() {
2492 let mut attributes = WebviewAttributes::new(tauri_utils::config::WebviewUrl::default());
2493 assert_eq!(
2494 browser_settings_from_webview_attributes(&attributes, false).background_color,
2495 0
2496 );
2497 assert_eq!(
2498 browser_settings_from_webview_attributes(&attributes, true).background_color,
2499 0xFFFF_FFFF
2500 );
2501
2502 attributes.transparent = true;
2503 assert_eq!(
2504 browser_settings_from_webview_attributes(&attributes, true).background_color,
2505 0
2506 );
2507
2508 attributes.background_color = Some(Color(1, 2, 3, 255));
2509 assert_eq!(
2510 browser_settings_from_webview_attributes(&attributes, true).background_color,
2511 0xFF01_0203
2512 );
2513 }
2514}