winio_ui_winui/widgets/
webview.rs1use std::rc::Rc;
2
3use cookie::Cookie;
4use futures_util::TryFutureExt;
5use inherit_methods_macro::inherit_methods;
6use send_wrapper::SendWrapper;
7use windows::{
8 Foundation::{TypedEventHandler, Uri},
9 Win32::Foundation::E_INVALIDARG,
10 core::{HSTRING, Interface, h},
11};
12use winio_callback::Callback;
13use winio_handle::AsContainer;
14use winio_primitive::{Point, Size};
15use winui3::Microsoft::{
16 UI::Xaml::Controls as MUXC,
17 Web::WebView2::Core::{
18 CoreWebView2Cookie, CoreWebView2CookieManager, CoreWebView2CookieSameSiteKind,
19 },
20};
21
22use crate::{Error, GlobalRuntime, Result, Widget};
23
24#[derive(Debug)]
25pub struct WebView {
26 on_navigating: SendWrapper<Rc<Callback>>,
27 on_navigated: SendWrapper<Rc<Callback>>,
28 handle: Widget,
29 view: MUXC::WebView2,
30}
31
32#[inherit_methods(from = "self.handle")]
33impl WebView {
34 pub async fn new(parent: impl AsContainer) -> Result<Self> {
35 #[cfg(feature = "webview-system")]
36 {
37 fn add_webview2sdk_path() {
38 use std::path::PathBuf;
39
40 use windows::{
41 Win32::{
42 System::LibraryLoader::{
43 AddDllDirectory, LOAD_LIBRARY_SEARCH_SYSTEM32,
44 LOAD_LIBRARY_SEARCH_USER_DIRS, SetDefaultDllDirectories,
45 },
46 UI::Shell::{CSIDL_WINDOWS, SHGetSpecialFolderPathW},
47 },
48 core::PCWSTR,
49 };
50
51 unsafe {
52 SetDefaultDllDirectories(
53 LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_SYSTEM32,
54 )
55 .ok();
56
57 let mut buffer = [0u16; 260];
58 if SHGetSpecialFolderPathW(None, &mut buffer, CSIDL_WINDOWS as _, false)
59 .ok()
60 .is_ok()
61 {
62 let windir =
63 widestring::U16CStr::from_ptr_str(buffer.as_ptr()).to_os_string();
64 let dlldir = PathBuf::from(windir).join(r"SystemApps\Shared\WebView2SDK");
65
66 if let Ok(dlldir) = widestring::U16CString::from_os_str(&dlldir) {
67 AddDllDirectory(PCWSTR(dlldir.as_ptr()));
68 }
69 }
70 }
71 }
72
73 use std::sync::Once;
74
75 static ADD_PATH: Once = Once::new();
76
77 ADD_PATH.call_once(add_webview2sdk_path);
78 }
79 let view = MUXC::WebView2::new()?;
80 view.EnsureCoreWebView2Async()?.await?;
81 let on_navigating = SendWrapper::new(Rc::new(Callback::new()));
82 {
83 let on_navigating = on_navigating.clone();
84 view.NavigationStarting(&TypedEventHandler::new(move |_, _| {
85 on_navigating.signal::<GlobalRuntime>(());
86 Ok(())
87 }))?;
88 }
89 let on_navigated = SendWrapper::new(Rc::new(Callback::new()));
90 {
91 let on_navigated = on_navigated.clone();
92 view.NavigationCompleted(&TypedEventHandler::new(move |_, _| {
93 on_navigated.signal::<GlobalRuntime>(());
94 Ok(())
95 }))?;
96 }
97 Ok(Self {
98 on_navigating,
99 on_navigated,
100 handle: Widget::new(parent, view.cast()?)?,
101 view,
102 })
103 }
104
105 pub fn is_visible(&self) -> Result<bool>;
106
107 pub fn set_visible(&mut self, v: bool) -> Result<()>;
108
109 pub fn is_enabled(&self) -> Result<bool>;
110
111 pub fn set_enabled(&mut self, v: bool) -> Result<()>;
112
113 pub fn loc(&self) -> Result<Point>;
114
115 pub fn set_loc(&mut self, v: Point) -> Result<()>;
116
117 pub fn size(&self) -> Result<Size>;
118
119 pub fn set_size(&mut self, v: Size) -> Result<()>;
120
121 pub fn source(&self) -> Result<String> {
122 Ok(self.view.Source()?.ToString()?.to_string_lossy())
123 }
124
125 pub fn set_source(&mut self, s: impl AsRef<str>) -> Result<()> {
126 let s = s.as_ref();
127 if s.is_empty() {
128 return self.set_html("");
129 }
130 self.view.SetSource(&Uri::CreateUri(&HSTRING::from(s))?)?;
131 Ok(())
132 }
133
134 pub fn set_html(&mut self, s: impl AsRef<str>) -> Result<()> {
135 self.view.NavigateToString(&HSTRING::from(s.as_ref()))?;
136 Ok(())
137 }
138
139 pub fn can_go_forward(&self) -> Result<bool> {
140 self.view.CanGoForward()
141 }
142
143 pub fn go_forward(&mut self) -> Result<()> {
144 self.view.GoForward()?;
145 Ok(())
146 }
147
148 pub fn can_go_back(&self) -> Result<bool> {
149 self.view.CanGoBack()
150 }
151
152 pub fn go_back(&mut self) -> Result<()> {
153 self.view.GoBack()?;
154 Ok(())
155 }
156
157 pub fn reload(&mut self) -> Result<()> {
158 self.view.Reload()?;
159 Ok(())
160 }
161
162 pub fn stop(&mut self) -> Result<()> {
163 self.view.CoreWebView2()?.Stop()?;
164 Ok(())
165 }
166
167 pub async fn wait_navigating(&self) {
168 self.on_navigating.wait().await;
169 }
170
171 pub async fn wait_navigated(&self) {
172 self.on_navigated.wait().await;
173 }
174
175 fn cookie_manager(&self) -> Result<CoreWebView2CookieManager> {
176 self.view.CoreWebView2()?.CookieManager()
177 }
178
179 pub async fn cookies(&self) -> Result<Vec<Cookie<'static>>> {
180 let cookies = self.cookie_manager()?.GetCookiesAsync(h!(""))?.await?;
181 let mut result = vec![];
182 for i in 0..cookies.Size()? {
183 let cookie = cookies.GetAt(i)?;
184 result.push(webview_cookie_to_cookie(&cookie)?);
185 }
186 Ok(result)
187 }
188
189 pub async fn set_cookie(&mut self, c: &Cookie<'_>) -> Result<()> {
190 let manager = self.cookie_manager()?;
191 manager.AddOrUpdateCookie(&cookie_to_webview_cookie(c, &manager)?)?;
192 Ok(())
193 }
194
195 pub async fn delete_cookie(&mut self, c: &Cookie<'_>) -> Result<()> {
196 let manager = self.cookie_manager()?;
197 manager.DeleteCookie(&cookie_to_webview_cookie(c, &manager)?)?;
198 Ok(())
199 }
200
201 pub fn run_javascript(
202 &mut self,
203 s: impl AsRef<str>,
204 ) -> Result<impl Future<Output = Result<String>> + 'static> {
205 self.view
206 .ExecuteScriptAsync(&HSTRING::from(s.as_ref()))
207 .map(|fut| fut.into_future().map_ok(|result| result.to_string_lossy()))
208 }
209}
210
211winio_handle::impl_as_widget!(WebView, handle);
212
213fn cookie_to_webview_cookie(
214 c: &Cookie<'_>,
215 manager: &CoreWebView2CookieManager,
216) -> Result<CoreWebView2Cookie> {
217 let cookie = manager.CreateCookie(
218 &HSTRING::from(c.name()),
219 &HSTRING::from(c.value()),
220 &HSTRING::from(c.domain().unwrap_or_default()),
221 &HSTRING::from(c.path().unwrap_or_default()),
222 )?;
223 if let Some(expires) = c.expires() {
224 match expires {
225 cookie::Expiration::Session => cookie.SetExpires(-1.0)?,
226 cookie::Expiration::DateTime(dt) => {
227 let timestamp = dt.unix_timestamp() as f64;
228 cookie.SetExpires(timestamp)?;
229 }
230 }
231 }
232 if let Some(is_secure) = c.secure() {
233 cookie.SetIsSecure(is_secure)?;
234 }
235 if let Some(is_http_only) = c.http_only() {
236 cookie.SetIsHttpOnly(is_http_only)?;
237 }
238 if let Some(same_site) = c.same_site() {
239 cookie.SetSameSite(match same_site {
240 cookie::SameSite::Lax => CoreWebView2CookieSameSiteKind::Lax,
241 cookie::SameSite::Strict => CoreWebView2CookieSameSiteKind::Strict,
242 cookie::SameSite::None => CoreWebView2CookieSameSiteKind::None,
243 })?;
244 }
245 Ok(cookie)
246}
247
248fn webview_cookie_to_cookie(c: &CoreWebView2Cookie) -> Result<Cookie<'static>> {
249 let name = c.Name()?.to_string_lossy();
250 let value = c.Value()?.to_string_lossy();
251 let domain = c.Domain()?.to_string_lossy();
252 let path = c.Path()?.to_string_lossy();
253 let expires = c.Expires()?;
254 let is_secure = c.IsSecure()?;
255 let is_http_only = c.IsHttpOnly()?;
256 let same_site = c.SameSite()?;
257 let is_session = c.IsSession()?;
258 let cookie = Cookie::build((name, value))
259 .domain(domain)
260 .path(path)
261 .expires(if is_session {
262 cookie::Expiration::Session
263 } else {
264 cookie::Expiration::DateTime(
265 time::OffsetDateTime::from_unix_timestamp(expires as _)
266 .map_err(|_| Error::from_hresult(E_INVALIDARG))?,
267 )
268 })
269 .secure(is_secure)
270 .http_only(is_http_only)
271 .same_site(match same_site {
272 CoreWebView2CookieSameSiteKind::Lax => cookie::SameSite::Lax,
273 CoreWebView2CookieSameSiteKind::Strict => cookie::SameSite::Strict,
274 CoreWebView2CookieSameSiteKind::None => cookie::SameSite::None,
275 _ => return Err(Error::from_hresult(E_INVALIDARG)),
276 })
277 .build();
278 Ok(cookie)
279}