Skip to main content

windows_webview/
controller.rs

1use super::*;
2use crate::handler::subscription;
3
4/// A 32-bit RGBA color, used for the browser's
5/// [default background](Controller::set_default_background_color).
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub struct Color {
8    pub r: u8,
9    pub g: u8,
10    pub b: u8,
11    pub a: u8,
12}
13
14impl Color {
15    /// A fully transparent color, letting the host window show through where the
16    /// page has not painted.
17    pub const TRANSPARENT: Self = Self {
18        r: 0,
19        g: 0,
20        b: 0,
21        a: 0,
22    };
23
24    fn from_raw(value: COREWEBVIEW2_COLOR) -> Self {
25        Self {
26            r: value.R,
27            g: value.G,
28            b: value.B,
29            a: value.A,
30        }
31    }
32
33    fn to_raw(self) -> COREWEBVIEW2_COLOR {
34        COREWEBVIEW2_COLOR {
35            A: self.a,
36            R: self.r,
37            G: self.g,
38            B: self.b,
39        }
40    }
41}
42
43/// Configures a [`Controller`] at creation time - its profile, whether it runs
44/// in private mode, and the color painted before content loads. Build one with
45/// the fluent setters and pass it to
46/// [`Environment::create_controller_with_options`].
47#[derive(Clone, Debug, Default)]
48pub struct ControllerOptions {
49    profile_name: Option<String>,
50    is_in_private_mode: bool,
51    default_background_color: Option<Color>,
52}
53
54impl ControllerOptions {
55    /// Creates options with all WebView2 defaults.
56    pub fn new() -> Self {
57        Self::default()
58    }
59
60    /// Sets the name of the profile the controller uses, isolating its cookies,
61    /// storage, and cache from other profiles in the same user-data folder.
62    pub fn profile_name(mut self, value: impl Into<String>) -> Self {
63        self.profile_name = Some(value.into());
64        self
65    }
66
67    /// Runs the controller in private (incognito) mode, leaving no profile data
68    /// on disk.
69    pub fn in_private_mode(mut self, value: bool) -> Self {
70        self.is_in_private_mode = value;
71        self
72    }
73
74    /// Sets the color painted behind the page before content loads. Use
75    /// [`Color::TRANSPARENT`] for a transparent browser from the first frame.
76    pub fn default_background_color(mut self, color: Color) -> Self {
77        self.default_background_color = Some(color);
78        self
79    }
80
81    pub(crate) fn create_controller<F: FnOnce(Result<Controller>) + 'static>(
82        &self,
83        environment: &ICoreWebView2Environment,
84        parent: HWND,
85        handler: F,
86    ) -> Result<()> {
87        let environment: ICoreWebView2Environment10 = environment.cast()?;
88        let options = unsafe { environment.CreateCoreWebView2ControllerOptions()? };
89
90        if let Some(profile_name) = &self.profile_name {
91            let profile_name = HSTRING::from(profile_name);
92            unsafe { options.SetProfileName(&profile_name).ok()? };
93        }
94        unsafe {
95            options
96                .SetIsInPrivateModeEnabled(self.is_in_private_mode)
97                .ok()?;
98        };
99        if let Some(color) = self.default_background_color {
100            let options: ICoreWebView2ControllerOptions3 = options.cast()?;
101            unsafe { options.SetDefaultBackgroundColor(color.to_raw()).ok()? };
102        }
103
104        let handler = handler::ControllerCompleted::create(handler);
105        unsafe { environment.CreateCoreWebView2ControllerWithOptions(parent, &options, &handler) }
106            .ok()
107    }
108}
109
110/// Hosts a WebView2 browser inside a parent window, controlling its bounds,
111/// visibility, and lifetime.
112pub struct Controller(pub(crate) ICoreWebView2Controller);
113
114impl Controller {
115    /// Returns the [`WebView`] used to navigate and script the hosted browser.
116    pub fn webview(&self) -> Result<WebView> {
117        unsafe { Ok(WebView(self.0.CoreWebView2()?)) }
118    }
119
120    /// Sets the bounds of the browser within the parent window, in pixels.
121    pub fn set_bounds(&self, left: i32, top: i32, right: i32, bottom: i32) -> Result<()> {
122        unsafe {
123            self.0
124                .SetBounds(RECT {
125                    left,
126                    top,
127                    right,
128                    bottom,
129                })
130                .ok()
131        }
132    }
133
134    /// Shows or hides the browser.
135    pub fn set_visible(&self, visible: bool) -> Result<()> {
136        unsafe { self.0.SetIsVisible(visible) }.ok()
137    }
138
139    /// Closes the browser and releases its resources.
140    pub fn close(&self) -> Result<()> {
141        unsafe { self.0.Close() }.ok()
142    }
143
144    /// Tells the browser that the parent window moved, so it can reposition any
145    /// popups and dialogs it owns. Call this from the host's `WM_MOVE` handler.
146    pub fn notify_parent_window_position_changed(&self) -> Result<()> {
147        unsafe { self.0.NotifyParentWindowPositionChanged() }.ok()
148    }
149
150    /// Returns `true` if files dragged from outside the application can be
151    /// dropped onto the browser.
152    pub fn allow_external_drop(&self) -> Result<bool> {
153        let source: ICoreWebView2Controller4 = self.0.cast()?;
154        Ok(unsafe { source.AllowExternalDrop()? }.as_bool())
155    }
156
157    /// Sets whether files dragged from outside the application can be dropped
158    /// onto the browser. Disable it when the host wants to handle drops itself.
159    pub fn set_allow_external_drop(&self, allow: bool) -> Result<()> {
160        let source: ICoreWebView2Controller4 = self.0.cast()?;
161        unsafe { source.SetAllowExternalDrop(allow) }.ok()
162    }
163
164    /// Returns the zoom factor applied to the page, where `1.0` is 100%.
165    pub fn zoom_factor(&self) -> f64 {
166        unsafe { self.0.ZoomFactor() }.unwrap_or(1.0)
167    }
168
169    /// Sets the zoom factor applied to the page, where `1.0` is 100%.
170    pub fn set_zoom_factor(&self, zoom_factor: f64) -> Result<()> {
171        unsafe { self.0.SetZoomFactor(zoom_factor) }.ok()
172    }
173
174    /// Returns the color painted behind the page before content loads and
175    /// wherever the page is transparent.
176    pub fn default_background_color(&self) -> Result<Color> {
177        let source: ICoreWebView2Controller2 = self.0.cast()?;
178        Ok(Color::from_raw(unsafe { source.DefaultBackgroundColor()? }))
179    }
180
181    /// Sets the color painted behind the page before content loads and wherever
182    /// the page is transparent. Use [`Color::TRANSPARENT`] to let the host window
183    /// show through; only fully opaque (`a = 255`) and fully transparent
184    /// (`a = 0`) colors are supported.
185    pub fn set_default_background_color(&self, color: Color) -> Result<()> {
186        let source: ICoreWebView2Controller2 = self.0.cast()?;
187        unsafe { source.SetDefaultBackgroundColor(color.to_raw()) }.ok()
188    }
189
190    /// Returns the scale used to rasterize page content, which the browser
191    /// derives from the monitor DPI.
192    pub fn rasterization_scale(&self) -> Result<f64> {
193        let source: ICoreWebView2Controller3 = self.0.cast()?;
194        unsafe { source.RasterizationScale() }
195    }
196
197    /// Sets the scale used to rasterize page content.
198    ///
199    /// Disable monitor-scale detection first so browser DPI changes do not
200    /// override this value.
201    pub fn set_rasterization_scale(&self, scale: f64) -> Result<()> {
202        let source: ICoreWebView2Controller3 = self.0.cast()?;
203        unsafe { source.SetRasterizationScale(scale) }.ok()
204    }
205
206    /// Returns `true` if the browser updates the
207    /// [rasterization scale](Self::rasterization_scale) automatically as the
208    /// monitor DPI changes.
209    pub fn should_detect_monitor_scale_changes(&self) -> Result<bool> {
210        let source: ICoreWebView2Controller3 = self.0.cast()?;
211        Ok(unsafe { source.ShouldDetectMonitorScaleChanges()? }.as_bool())
212    }
213
214    /// Sets whether the browser updates the
215    /// [rasterization scale](Self::rasterization_scale) automatically as the
216    /// monitor DPI changes. Disable it to manage the scale yourself.
217    pub fn set_should_detect_monitor_scale_changes(&self, detect: bool) -> Result<()> {
218        let source: ICoreWebView2Controller3 = self.0.cast()?;
219        unsafe { source.SetShouldDetectMonitorScaleChanges(detect) }.ok()
220    }
221
222    /// Moves focus into the browser, as if focus arrived for the given
223    /// [`reason`](MoveFocusReason). Call this from the host's `WM_SETFOCUS`
224    /// handler so the browser takes keyboard focus.
225    pub fn move_focus(&self, reason: MoveFocusReason) -> Result<()> {
226        unsafe { self.0.MoveFocus(reason.to_raw()) }.ok()
227    }
228
229    /// Subscribes to the got-focus event, raised when the browser gains focus.
230    pub fn on_got_focus<F: FnMut() + 'static>(&self, handler: F) -> Result<EventRegistration> {
231        let handler = handler::FocusChanged::create(handler);
232        let token = unsafe { self.0.add_GotFocus(&handler)? };
233        let source = self.0.clone();
234        Ok(EventRegistration::new(move || {
235            let _ = unsafe { source.remove_GotFocus(token) };
236        }))
237    }
238
239    /// Subscribes to the lost-focus event, raised when the browser loses focus.
240    pub fn on_lost_focus<F: FnMut() + 'static>(&self, handler: F) -> Result<EventRegistration> {
241        let handler = handler::FocusChanged::create(handler);
242        let token = unsafe { self.0.add_LostFocus(&handler)? };
243        let source = self.0.clone();
244        Ok(EventRegistration::new(move || {
245            let _ = unsafe { source.remove_LostFocus(token) };
246        }))
247    }
248
249    subscription! {
250        /// Subscribes to the move-focus-requested event, raised when focus is
251        /// leaving the browser (for example the user tabbed past the last
252        /// element). Move focus to the appropriate host control and call
253        /// [`MoveFocusRequestedArgs::set_handled`]. Wiring this is what keeps Tab
254        /// navigation and screen readers working when the browser is embedded.
255        on_move_focus_requested(MoveFocusRequestedArgs) =>
256            MoveFocusRequested, add_MoveFocusRequested / remove_MoveFocusRequested
257    }
258
259    subscription! {
260        /// Subscribes to the accelerator-key-pressed event, raised for
261        /// browser-level keys (such as function keys or a key combined with Ctrl
262        /// or Alt) before the page handles them. Inspect
263        /// [`AcceleratorKeyPressedArgs`] to implement application keyboard
264        /// shortcuts.
265        on_accelerator_key_pressed(AcceleratorKeyPressedArgs) =>
266            AcceleratorKeyPressed, add_AcceleratorKeyPressed / remove_AcceleratorKeyPressed
267    }
268}