Skip to main content

tauri_runtime_cef/
offscreen.rs

1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5use std::sync::{
6  Arc, Mutex, Weak,
7  atomic::{AtomicBool, Ordering},
8};
9
10use tauri_runtime::dpi::Rect;
11
12/// A cursor notification on the CEF UI thread. Any custom cursor buffer is only
13/// valid during the callback.
14pub type CursorHandler = dyn Fn(cef::CursorType, Option<&cef::CursorInfo>) + Send + Sync;
15
16/// Windowless browser configuration. The application owns painting, input/IME
17/// forwarding and composition; Tauri keeps its IPC, protocols and browser lifecycle.
18/// Offscreen webviews use Alloy style and cannot be reparented.
19///
20/// Enable `settings.windowless_rendering_enabled` through [`crate::Cef::with_settings`]
21/// before creating an offscreen webview. Use [`crate::Cef::on_window_event`] for
22/// native input and redraws, and [`crate::Webview::browser`] to reach CEF's host API.
23///
24/// The native window and event types are `tauri-winit`'s: depend on the same
25/// `tauri-winit` version this crate uses to name them.
26#[derive(Clone)]
27pub struct Offscreen {
28  /// Delivers shared resources through `RenderHandler::on_accelerated_paint`.
29  /// Open and copy them during the callback, respecting CEF's resource lifetime.
30  pub shared_texture_enabled: bool,
31  /// Requires the application to call `BrowserHost::send_external_begin_frame`.
32  pub external_begin_frame_enabled: bool,
33  /// Handles cursor changes; other display notifications remain managed by Tauri.
34  pub cursor_handler: Option<Arc<CursorHandler>>,
35  pub(crate) render_handler: Arc<dyn Fn(OffscreenView) -> cef::RenderHandler + Send + Sync>,
36}
37
38impl Offscreen {
39  /// Creates a render handler for each webview on the CEF UI thread.
40  ///
41  /// The handler must implement `view_rect` and a paint callback. Read the current
42  /// bounds and scale from `view`; call [`OffscreenView::request_redraw`] after painting.
43  /// CEF view rectangles use logical pixels, while paint buffers use physical pixels.
44  /// CEF callbacks must not block on queued Tauri window operations.
45  pub fn new<F: Fn(OffscreenView) -> cef::RenderHandler + Send + Sync + 'static>(
46    handler: F,
47  ) -> Self {
48    Self {
49      shared_texture_enabled: false,
50      external_begin_frame_enabled: false,
51      cursor_handler: None,
52      render_handler: Arc::new(handler),
53    }
54  }
55
56  pub(crate) fn window_info(&self, parent: cef::sys::cef_window_handle_t) -> cef::WindowInfo {
57    let mut info = cef::WindowInfo::default().set_as_windowless(parent);
58    info.shared_texture_enabled = i32::from(self.shared_texture_enabled);
59    info.external_begin_frame_enabled = i32::from(self.external_begin_frame_enabled);
60    info
61  }
62}
63
64/// Live layout of one offscreen webview, shared with its render handler.
65#[derive(Clone)]
66pub struct OffscreenView {
67  /// Weak, so a render handler CEF still holds does not keep a closed window alive.
68  pub(crate) window: Weak<dyn winit::window::Window>,
69  pub(crate) bounds: Arc<Mutex<Rect>>,
70  pub(crate) visible: Arc<AtomicBool>,
71}
72
73impl OffscreenView {
74  /// Native host used for scale, screen coordinates, IME and redraw requests.
75  /// `None` once the runtime released the window; do not keep the returned
76  /// reference past `Destroyed`.
77  pub fn window(&self) -> Option<Arc<dyn winit::window::Window>> {
78    self.window.upgrade()
79  }
80
81  /// Asks the host window to redraw, if it still exists.
82  pub fn request_redraw(&self) {
83    if let Some(window) = self.window.upgrade() {
84      window.request_redraw();
85    }
86  }
87
88  /// Bounds relative to the host, in logical pixels.
89  pub fn bounds(&self) -> Rect {
90    *self.bounds.lock().unwrap_or_else(|e| e.into_inner())
91  }
92
93  /// Requested webview visibility, independent of native window occlusion.
94  pub fn is_visible(&self) -> bool {
95    self.visible.load(Ordering::Relaxed)
96  }
97}
98
99#[cfg(test)]
100mod tests {
101  use super::*;
102
103  #[test]
104  fn offscreen_options_select_alloy_and_the_requested_paint_mode() {
105    let mut options = Offscreen::new(|_| unreachable!());
106    let software = options.window_info(Default::default());
107    assert_eq!(software.windowless_rendering_enabled, 1);
108    assert_eq!(software.runtime_style, cef::RuntimeStyle::ALLOY);
109    assert_eq!(software.shared_texture_enabled, 0);
110    assert_eq!(software.external_begin_frame_enabled, 0);
111
112    options.shared_texture_enabled = true;
113    options.external_begin_frame_enabled = true;
114    let accelerated = options.window_info(Default::default());
115    assert_eq!(accelerated.shared_texture_enabled, 1);
116    assert_eq!(accelerated.external_begin_frame_enabled, 1);
117  }
118}