1use std::{cell::RefCell, rc::Rc};
2
3use cookie::Cookie;
4use futures_util::FutureExt;
5use webview2::{
6 COREWEBVIEW2_COOKIE_SAME_SITE_KIND_LAX, COREWEBVIEW2_COOKIE_SAME_SITE_KIND_NONE,
7 COREWEBVIEW2_COOKIE_SAME_SITE_KIND_STRICT, CreateCoreWebView2Environment, ICoreWebView2,
8 ICoreWebView2_2, ICoreWebView2Controller, ICoreWebView2Cookie, ICoreWebView2CookieList,
9 ICoreWebView2CookieManager, ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
10 ICoreWebView2CreateCoreWebView2ControllerCompletedHandler_Impl,
11 ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
12 ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler_Impl, ICoreWebView2Environment,
13 ICoreWebView2ExecuteScriptCompletedHandler, ICoreWebView2ExecuteScriptCompletedHandler_Impl,
14 ICoreWebView2GetCookiesCompletedHandler, ICoreWebView2GetCookiesCompletedHandler_Impl,
15 ICoreWebView2NavigationCompletedEventArgs, ICoreWebView2NavigationCompletedEventHandler,
16 ICoreWebView2NavigationCompletedEventHandler_Impl, ICoreWebView2NavigationStartingEventArgs,
17 ICoreWebView2NavigationStartingEventHandler, ICoreWebView2NavigationStartingEventHandler_Impl,
18};
19use windows::{
20 Win32::Foundation::{E_FAIL, E_INVALIDARG, HWND, RECT},
21 core::{HRESULT, HSTRING, Interface, PCWSTR, Ref, implement},
22};
23use windows_sys::Win32::{Foundation::ERROR_CANCELLED, UI::HiDpi::GetDpiForWindow};
24use winio_callback::Callback;
25use winio_handle::{AsContainer, AsWidget, BorrowedWidget};
26use winio_primitive::{Point, Rect, Size};
27use winio_ui_windows_common::CoTaskMemPtr;
28
29use crate::{Error, Result, widgets::with_u16c};
30
31#[derive(Debug)]
32pub struct WebView {
33 host: ICoreWebView2Controller,
34 view: ICoreWebView2,
35 navigating: Rc<Callback>,
36 navigated: Rc<Callback>,
37}
38
39impl WebView {
40 pub async fn new(parent: impl AsContainer) -> Result<Self> {
41 let (tx, rx) = local_sync::oneshot::channel();
42 let hwnd = parent.as_container().as_win32();
43 unsafe {
44 CreateCoreWebView2Environment(&CreateEnvHandler::create(move |env| {
45 let env = env?;
46 let env = env.ok()?;
47 env.CreateCoreWebView2Controller(
48 HWND(hwnd),
49 &CreateControllerHandler::create(move |host| {
50 let host = host?;
51 let host = host.ok()?;
52 let view = host.CoreWebView2()?;
53 tx.send((host.clone(), view)).ok();
54 Ok(())
55 }),
56 )?;
57 Ok(())
58 }))?;
59 }
60 let (host, view) = rx.await.map_err(|_| Error::from_hresult(E_FAIL))?;
61 let navigating = Rc::new(Callback::new());
62 unsafe {
63 let navigating = navigating.clone();
64 view.NavigationStarting(&NavStartingHandler::create(move |_, _| {
65 navigating.signal::<()>(());
66 Ok(())
67 }))?;
68 }
69 let navigated = Rc::new(Callback::new());
70 unsafe {
71 let navigated = navigated.clone();
72 view.NavigationCompleted(&NavCompletedHandler::create(move |_, _| {
73 navigated.signal::<()>(());
74 Ok(())
75 }))?;
76 }
77 unsafe {
78 host.SetIsVisible(true)?;
79 }
80 Ok(Self {
81 host,
82 view,
83 navigating,
84 navigated,
85 })
86 }
87
88 fn dpi(&self) -> Result<f64> {
89 unsafe {
90 let hwnd = self.host.ParentWindow()?;
91 Ok(GetDpiForWindow(hwnd.0) as f64 / 96.0)
92 }
93 }
94
95 fn rect(&self) -> Result<Rect> {
96 let rect = unsafe { self.host.Bounds() }?;
97 Ok(Rect::new(
98 Point::new(rect.left as _, rect.top as _),
99 Size::new((rect.right - rect.left) as _, (rect.bottom - rect.top) as _),
100 ) / self.dpi()?)
101 }
102
103 fn set_rect(&mut self, r: Rect) -> Result<()> {
104 let r = r * self.dpi()?;
105 unsafe {
106 self.host.SetBounds(RECT {
107 left: r.origin.x as _,
108 top: r.origin.y as _,
109 right: (r.origin.x + r.size.width) as _,
110 bottom: (r.origin.y + r.size.height) as _,
111 })?;
112 }
113 Ok(())
114 }
115
116 pub fn is_visible(&self) -> Result<bool> {
117 unsafe { Ok(self.host.IsVisible()?.as_bool()) }
118 }
119
120 pub fn set_visible(&mut self, v: bool) -> Result<()> {
121 unsafe {
122 self.host.SetIsVisible(v)?;
123 Ok(())
124 }
125 }
126
127 pub fn is_enabled(&self) -> Result<bool> {
128 Ok(true)
129 }
130
131 pub fn set_enabled(&mut self, _: bool) -> Result<()> {
132 Ok(())
133 }
134
135 pub fn loc(&self) -> Result<Point> {
136 Ok(self.rect()?.origin)
137 }
138
139 pub fn set_loc(&mut self, p: Point) -> Result<()> {
140 let mut rect = self.rect()?;
141 rect.origin = p;
142 self.set_rect(rect)
143 }
144
145 pub fn size(&self) -> Result<Size> {
146 Ok(self.rect()?.size)
147 }
148
149 pub fn set_size(&mut self, v: Size) -> Result<()> {
150 let mut rect = self.rect()?;
151 rect.size = v;
152 self.set_rect(rect)
153 }
154
155 pub fn source(&self) -> Result<String> {
156 unsafe {
157 let source = CoTaskMemPtr::new(self.view.Source()?.0);
158 source.to_string()
159 }
160 }
161
162 pub fn set_source(&mut self, s: impl AsRef<str>) -> Result<()> {
163 let s = s.as_ref();
164 if s.is_empty() {
165 return self.set_html("");
166 }
167 with_u16c(s, |s| unsafe {
168 self.view.Navigate(PCWSTR(s.as_ptr()))?;
169 Ok(())
170 })
171 }
172
173 pub fn set_html(&mut self, s: impl AsRef<str>) -> Result<()> {
174 with_u16c(s.as_ref(), |s| unsafe {
175 self.view.NavigateToString(PCWSTR(s.as_ptr()))?;
176 Ok(())
177 })
178 }
179
180 pub fn can_go_forward(&self) -> Result<bool> {
181 unsafe { Ok(self.view.CanGoForward()?.as_bool()) }
182 }
183
184 pub fn go_forward(&mut self) -> Result<()> {
185 unsafe {
186 self.view.GoForward()?;
187 Ok(())
188 }
189 }
190
191 pub fn can_go_back(&self) -> Result<bool> {
192 unsafe { Ok(self.view.CanGoBack()?.as_bool()) }
193 }
194
195 pub fn go_back(&mut self) -> Result<()> {
196 unsafe {
197 self.view.GoBack()?;
198 Ok(())
199 }
200 }
201
202 pub fn reload(&mut self) -> Result<()> {
203 unsafe {
204 self.view.Reload()?;
205 Ok(())
206 }
207 }
208
209 pub fn stop(&mut self) -> Result<()> {
210 unsafe {
211 self.view.Stop()?;
212 Ok(())
213 }
214 }
215
216 pub async fn wait_navigating(&self) {
217 self.navigating.wait().await;
218 }
219
220 pub async fn wait_navigated(&self) {
221 self.navigated.wait().await;
222 }
223
224 fn cookie_manager(&self) -> Result<ICoreWebView2CookieManager> {
225 unsafe { self.view.cast::<ICoreWebView2_2>()?.CookieManager() }
226 }
227
228 pub async fn cookies(&self) -> Result<Vec<Cookie<'static>>> {
229 let (tx, rx) = local_sync::oneshot::channel();
230 let handler = GetCookiesHandler::create(move |result| {
231 fn conv_cookies(cookies: Ref<ICoreWebView2CookieList>) -> Result<Vec<Cookie<'static>>> {
232 let list = cookies.ok()?;
233 let mut cookies = vec![];
234 for i in 0..unsafe { list.Count()? } {
235 let cookie = unsafe { list.GetValueAtIndex(i)? };
236 cookies.push(webview_cookie_to_cookie(&cookie)?);
237 }
238 Ok(cookies)
239 }
240 tx.send(result.map(conv_cookies)).ok();
241 Ok(())
242 });
243 unsafe { self.cookie_manager()?.GetCookies(None, &handler)? };
244 rx.await
245 .map_err(|_| Error::from_hresult(HRESULT::from_win32(ERROR_CANCELLED)))??
246 }
247
248 pub async fn set_cookie(&mut self, c: &Cookie<'_>) -> Result<()> {
249 unsafe {
250 let manager = self.cookie_manager()?;
251 manager.AddOrUpdateCookie(&cookie_to_webview_cookie(c, &manager)?)?;
252 }
253 Ok(())
254 }
255
256 pub async fn delete_cookie(&mut self, c: &Cookie<'_>) -> Result<()> {
257 unsafe {
258 let manager = self.cookie_manager()?;
259 manager.DeleteCookie(&cookie_to_webview_cookie(c, &manager)?)?;
260 }
261 Ok(())
262 }
263
264 pub fn run_javascript(
265 &mut self,
266 s: impl AsRef<str>,
267 ) -> Result<impl Future<Output = Result<String>> + 'static> {
268 let s = s.as_ref();
269 let (tx, rx) = local_sync::oneshot::channel();
270 with_u16c(s, |s| unsafe {
271 self.view.ExecuteScript(
272 PCWSTR(s.as_ptr()),
273 &ExecuteScriptHandler::create(move |result| {
274 tx.send(result.map(|s| s.to_hstring())).ok();
275 Ok(())
276 }),
277 )?;
278 Ok(())
279 })?;
280 Ok(rx.into_future().map(|result| {
281 result
282 .map_err(|_| Error::from_hresult(HRESULT::from_win32(ERROR_CANCELLED)))
283 .flatten()
284 .map(|s| s.to_string_lossy())
285 }))
286 }
287}
288
289impl AsWidget for WebView {
290 fn as_widget(&self) -> BorrowedWidget<'_> {
291 unimplemented!("cannot get HWND from WebView2")
292 }
293}
294
295fn cookie_to_webview_cookie(
296 c: &Cookie<'_>,
297 manager: &ICoreWebView2CookieManager,
298) -> Result<ICoreWebView2Cookie> {
299 unsafe {
300 let name = HSTRING::from(c.name());
301 let value = HSTRING::from(c.value());
302 let domain = HSTRING::from(c.domain().unwrap_or_default());
303 let path = HSTRING::from(c.path().unwrap_or_default());
304 let cookie = manager.CreateCookie(
305 PCWSTR(name.as_ptr()),
306 PCWSTR(value.as_ptr()),
307 PCWSTR(domain.as_ptr()),
308 PCWSTR(path.as_ptr()),
309 )?;
310 if let Some(expires) = c.expires() {
311 match expires {
312 cookie::Expiration::Session => cookie.SetExpires(-1.0)?,
313 cookie::Expiration::DateTime(dt) => {
314 let timestamp = dt.unix_timestamp() as f64;
315 cookie.SetExpires(timestamp)?;
316 }
317 }
318 }
319 if let Some(is_secure) = c.secure() {
320 cookie.SetIsSecure(is_secure)?;
321 }
322 if let Some(is_http_only) = c.http_only() {
323 cookie.SetIsHttpOnly(is_http_only)?;
324 }
325 if let Some(same_site) = c.same_site() {
326 cookie.SetSameSite(match same_site {
327 cookie::SameSite::Lax => COREWEBVIEW2_COOKIE_SAME_SITE_KIND_LAX,
328 cookie::SameSite::Strict => COREWEBVIEW2_COOKIE_SAME_SITE_KIND_STRICT,
329 cookie::SameSite::None => COREWEBVIEW2_COOKIE_SAME_SITE_KIND_NONE,
330 })?;
331 }
332 Ok(cookie)
333 }
334}
335
336fn webview_cookie_to_cookie(c: &ICoreWebView2Cookie) -> Result<Cookie<'static>> {
337 let name = unsafe { CoTaskMemPtr::new(c.Name()?.0) };
338 let value = unsafe { CoTaskMemPtr::new(c.Value()?.0) };
339 let domain = unsafe { CoTaskMemPtr::new(c.Domain()?.0) };
340 let path = unsafe { CoTaskMemPtr::new(c.Path()?.0) };
341 let expires = unsafe { c.Expires() }?;
342 let is_secure = unsafe { c.IsSecure()?.as_bool() };
343 let is_http_only = unsafe { c.IsHttpOnly()?.as_bool() };
344 let same_site = unsafe { c.SameSite()? };
345 let is_session = unsafe { c.IsSession()?.as_bool() };
346 let cookie = Cookie::build((unsafe { name.to_string()? }, unsafe { value.to_string()? }))
347 .domain(unsafe { domain.to_string()? })
348 .path(unsafe { path.to_string()? })
349 .expires(if is_session {
350 cookie::Expiration::Session
351 } else {
352 cookie::Expiration::DateTime(
353 time::OffsetDateTime::from_unix_timestamp(expires as _)
354 .map_err(|_| Error::from_hresult(E_INVALIDARG))?,
355 )
356 })
357 .secure(is_secure)
358 .http_only(is_http_only)
359 .same_site(match same_site {
360 COREWEBVIEW2_COOKIE_SAME_SITE_KIND_LAX => cookie::SameSite::Lax,
361 COREWEBVIEW2_COOKIE_SAME_SITE_KIND_STRICT => cookie::SameSite::Strict,
362 COREWEBVIEW2_COOKIE_SAME_SITE_KIND_NONE => cookie::SameSite::None,
363 _ => return Err(Error::from_hresult(E_INVALIDARG)),
364 })
365 .build();
366 Ok(cookie)
367}
368
369#[implement(
370 ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
371 Agile = false
372)]
373struct CreateEnvHandler<F>
374where
375 F: FnOnce(Result<Ref<ICoreWebView2Environment>>) -> Result<()> + 'static,
376{
377 f: RefCell<Option<F>>,
378}
379
380impl<F> CreateEnvHandler<F>
381where
382 F: FnOnce(Result<Ref<ICoreWebView2Environment>>) -> Result<()> + 'static,
383{
384 pub fn create(f: F) -> ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler {
385 Self {
386 f: RefCell::new(Some(f)),
387 }
388 .into()
389 }
390}
391
392impl<F> ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler_Impl for CreateEnvHandler_Impl<F>
393where
394 F: FnOnce(Result<Ref<ICoreWebView2Environment>>) -> Result<()> + 'static,
395{
396 fn Invoke(
397 &self,
398 errorcode: HRESULT,
399 createdenvironment: Ref<ICoreWebView2Environment>,
400 ) -> Result<()> {
401 let f = self.f.borrow_mut().take();
402 if let Some(f) = f {
403 f(errorcode.map(|| createdenvironment))
404 } else {
405 Ok(())
406 }
407 }
408}
409
410#[implement(
411 ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
412 Agile = false
413)]
414struct CreateControllerHandler<F>
415where
416 F: FnOnce(Result<Ref<ICoreWebView2Controller>>) -> Result<()> + 'static,
417{
418 f: RefCell<Option<F>>,
419}
420
421impl<F> CreateControllerHandler<F>
422where
423 F: FnOnce(Result<Ref<ICoreWebView2Controller>>) -> Result<()> + 'static,
424{
425 pub fn create(f: F) -> ICoreWebView2CreateCoreWebView2ControllerCompletedHandler {
426 Self {
427 f: RefCell::new(Some(f)),
428 }
429 .into()
430 }
431}
432
433impl<F> ICoreWebView2CreateCoreWebView2ControllerCompletedHandler_Impl
434 for CreateControllerHandler_Impl<F>
435where
436 F: FnOnce(Result<Ref<ICoreWebView2Controller>>) -> Result<()> + 'static,
437{
438 fn Invoke(
439 &self,
440 errorcode: HRESULT,
441 createdcontroller: Ref<ICoreWebView2Controller>,
442 ) -> Result<()> {
443 let f = self.f.borrow_mut().take();
444 if let Some(f) = f {
445 f(errorcode.map(|| createdcontroller))
446 } else {
447 Ok(())
448 }
449 }
450}
451
452#[implement(ICoreWebView2NavigationStartingEventHandler, Agile = false)]
453struct NavStartingHandler<F>
454where
455 F: Fn(Ref<ICoreWebView2>, Ref<ICoreWebView2NavigationStartingEventArgs>) -> Result<()>
456 + 'static,
457{
458 f: F,
459}
460
461impl<F> NavStartingHandler<F>
462where
463 F: Fn(Ref<ICoreWebView2>, Ref<ICoreWebView2NavigationStartingEventArgs>) -> Result<()>
464 + 'static,
465{
466 pub fn create(f: F) -> ICoreWebView2NavigationStartingEventHandler {
467 Self { f }.into()
468 }
469}
470
471impl<F> ICoreWebView2NavigationStartingEventHandler_Impl for NavStartingHandler_Impl<F>
472where
473 F: Fn(Ref<ICoreWebView2>, Ref<ICoreWebView2NavigationStartingEventArgs>) -> Result<()>
474 + 'static,
475{
476 fn Invoke(
477 &self,
478 sender: Ref<ICoreWebView2>,
479 args: Ref<ICoreWebView2NavigationStartingEventArgs>,
480 ) -> Result<()> {
481 (self.f)(sender, args)
482 }
483}
484
485#[implement(ICoreWebView2NavigationCompletedEventHandler, Agile = false)]
486struct NavCompletedHandler<F>
487where
488 F: Fn(Ref<ICoreWebView2>, Ref<ICoreWebView2NavigationCompletedEventArgs>) -> Result<()>
489 + 'static,
490{
491 f: F,
492}
493
494impl<F> NavCompletedHandler<F>
495where
496 F: Fn(Ref<ICoreWebView2>, Ref<ICoreWebView2NavigationCompletedEventArgs>) -> Result<()>
497 + 'static,
498{
499 pub fn create(f: F) -> ICoreWebView2NavigationCompletedEventHandler {
500 Self { f }.into()
501 }
502}
503
504impl<F> ICoreWebView2NavigationCompletedEventHandler_Impl for NavCompletedHandler_Impl<F>
505where
506 F: Fn(Ref<ICoreWebView2>, Ref<ICoreWebView2NavigationCompletedEventArgs>) -> Result<()>
507 + 'static,
508{
509 fn Invoke(
510 &self,
511 sender: Ref<ICoreWebView2>,
512 args: Ref<ICoreWebView2NavigationCompletedEventArgs>,
513 ) -> Result<()> {
514 (self.f)(sender, args)
515 }
516}
517
518#[implement(ICoreWebView2GetCookiesCompletedHandler, Agile = false)]
519struct GetCookiesHandler<F>
520where
521 F: FnOnce(Result<Ref<ICoreWebView2CookieList>>) -> Result<()> + 'static,
522{
523 f: RefCell<Option<F>>,
524}
525
526impl<F> GetCookiesHandler<F>
527where
528 F: FnOnce(Result<Ref<ICoreWebView2CookieList>>) -> Result<()> + 'static,
529{
530 pub fn create(f: F) -> ICoreWebView2GetCookiesCompletedHandler {
531 Self {
532 f: RefCell::new(Some(f)),
533 }
534 .into()
535 }
536}
537
538impl<F> ICoreWebView2GetCookiesCompletedHandler_Impl for GetCookiesHandler_Impl<F>
539where
540 F: FnOnce(Result<Ref<ICoreWebView2CookieList>>) -> Result<()> + 'static,
541{
542 fn Invoke(&self, errorcode: HRESULT, cookie_list: Ref<ICoreWebView2CookieList>) -> Result<()> {
543 let f = self.f.borrow_mut().take();
544 if let Some(f) = f {
545 f(errorcode.map(|| cookie_list))
546 } else {
547 Ok(())
548 }
549 }
550}
551
552#[implement(ICoreWebView2ExecuteScriptCompletedHandler, Agile = false)]
553struct ExecuteScriptHandler<F>
554where
555 F: FnOnce(Result<PCWSTR>) -> Result<()> + 'static,
556{
557 f: RefCell<Option<F>>,
558}
559
560impl<F> ExecuteScriptHandler<F>
561where
562 F: FnOnce(Result<PCWSTR>) -> Result<()> + 'static,
563{
564 pub fn create(f: F) -> ICoreWebView2ExecuteScriptCompletedHandler {
565 Self {
566 f: RefCell::new(Some(f)),
567 }
568 .into()
569 }
570}
571
572impl<F> ICoreWebView2ExecuteScriptCompletedHandler_Impl for ExecuteScriptHandler_Impl<F>
573where
574 F: FnOnce(Result<PCWSTR>) -> Result<()> + 'static,
575{
576 fn Invoke(&self, errorcode: HRESULT, resultobjectasjson: &PCWSTR) -> Result<()> {
577 let f = self.f.borrow_mut().take();
578 if let Some(f) = f {
579 f(errorcode.map(|| *resultobjectasjson))
580 } else {
581 Ok(())
582 }
583 }
584}