Skip to main content

iced_webview/webview/
advanced.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use iced::advanced::image as core_image;
5use iced::advanced::{
6    self, layout,
7    renderer::{self},
8    widget::Tree,
9    Clipboard, Layout, Shell, Widget,
10};
11use iced::keyboard;
12use iced::mouse::{self, Interaction};
13use iced::{Element, Point, Size, Task};
14use iced::{Event, Length, Rectangle};
15use url::Url;
16
17use crate::{engines, ImageInfo, PageType, ViewId};
18
19#[cfg(any(feature = "servo", feature = "cef"))]
20use crate::webview::shader_widget::WebViewPrimitive;
21#[cfg(any(feature = "servo", feature = "cef"))]
22use iced::widget::shader;
23
24#[allow(missing_docs)]
25#[derive(Debug, Clone, PartialEq)]
26pub enum Action {
27    CloseView(ViewId),
28    CreateView(PageType),
29    GoBackward(ViewId),
30    GoForward(ViewId),
31    GoToUrl(ViewId, Url),
32    Refresh(ViewId),
33    SendKeyboardEvent(ViewId, keyboard::Event),
34    SendMouseEvent(ViewId, mouse::Event, Point),
35    /// Call this periodically to update a view
36    Update(ViewId),
37    /// Call this periodically to update a view(s)
38    UpdateAll,
39    Resize(Size<u32>),
40    /// Copy the current text selection to clipboard
41    CopySelection(ViewId),
42    /// Internal: carries the result of a URL fetch for engines without native URL support.
43    /// On success returns `(html, css_cache)`.
44    FetchComplete(
45        ViewId,
46        String,
47        Result<(String, HashMap<String, String>), String>,
48    ),
49    /// Internal: carries the result of an image fetch.
50    /// The bool is `redraw_on_ready`, the u64 is the navigation epoch.
51    ImageFetchComplete(ViewId, String, Result<Vec<u8>, String>, bool, u64),
52}
53
54/// The Advanced WebView widget that creates and shows webview(s)
55pub struct WebView<Engine, Message>
56where
57    Engine: engines::Engine,
58{
59    engine: Engine,
60    view_size: Size<u32>,
61    scale_factor: f32,
62    on_close_view: Option<Box<dyn Fn(ViewId) -> Message>>,
63    on_create_view: Option<Box<dyn Fn(ViewId) -> Message>>,
64    on_url_change: Option<Box<dyn Fn(ViewId, String) -> Message>>,
65    urls: Vec<(ViewId, String)>,
66    on_title_change: Option<Box<dyn Fn(ViewId, String) -> Message>>,
67    titles: Vec<(ViewId, String)>,
68    on_copy: Option<Box<dyn Fn(String) -> Message>>,
69    action_mapper: Option<Arc<dyn Fn(Action) -> Message + Send + Sync>>,
70    inflight_images: usize,
71    nav_epochs: HashMap<ViewId, u64>,
72}
73
74impl<Engine: engines::Engine + Default, Message: Send + Clone + 'static> Default
75    for WebView<Engine, Message>
76{
77    fn default() -> Self {
78        WebView {
79            engine: Engine::default(),
80            view_size: Size::new(1920, 1080),
81            scale_factor: 1.0,
82            on_close_view: None,
83            on_create_view: None,
84            on_url_change: None,
85            urls: Vec::new(),
86            on_title_change: None,
87            titles: Vec::new(),
88            on_copy: None,
89            action_mapper: None,
90            inflight_images: 0,
91            nav_epochs: HashMap::new(),
92        }
93    }
94}
95
96impl<Engine: engines::Engine + Default, Message: Send + Clone + 'static> WebView<Engine, Message> {
97    /// Create new Advanced Webview widget
98    pub fn new() -> Self {
99        Self::default()
100    }
101
102    /// Set the display scale factor for HiDPI rendering.
103    pub fn set_scale_factor(&mut self, scale: f32) {
104        self.scale_factor = scale;
105        self.engine.set_scale_factor(scale);
106    }
107
108    /// Subscribe to create view events
109    pub fn on_create_view(mut self, on_create_view: impl Fn(usize) -> Message + 'static) -> Self {
110        self.on_create_view = Some(Box::new(on_create_view));
111        self
112    }
113
114    /// Subscribe to close view events
115    pub fn on_close_view(mut self, on_close_view: impl Fn(usize) -> Message + 'static) -> Self {
116        self.on_close_view = Some(Box::new(on_close_view));
117        self
118    }
119
120    /// Subscribe to url change events
121    pub fn on_url_change(
122        mut self,
123        on_url_change: impl Fn(ViewId, String) -> Message + 'static,
124    ) -> Self {
125        self.on_url_change = Some(Box::new(on_url_change));
126        self
127    }
128
129    /// Subscribe to title change events
130    pub fn on_title_change(
131        mut self,
132        on_title_change: impl Fn(ViewId, String) -> Message + 'static,
133    ) -> Self {
134        self.on_title_change = Some(Box::new(on_title_change));
135        self
136    }
137
138    /// Subscribe to copy events (text selection copied via Ctrl+C / Cmd+C)
139    pub fn on_copy(mut self, on_copy: impl Fn(String) -> Message + 'static) -> Self {
140        self.on_copy = Some(Box::new(on_copy));
141        self
142    }
143
144    /// Provide a mapper from Action to Message so the webview can spawn async
145    /// tasks (e.g. URL fetches) that route back through the update loop.
146    /// Required for URL navigation on engines that don't handle URLs natively.
147    pub fn on_action(mut self, mapper: impl Fn(Action) -> Message + Send + Sync + 'static) -> Self {
148        self.action_mapper = Some(Arc::new(mapper));
149        self
150    }
151
152    /// Passes update to webview
153    pub fn update(&mut self, action: Action) -> Task<Message> {
154        let mut tasks = Vec::new();
155
156        // Check url & title for changes and callback if so
157        for (id, url) in self.urls.iter_mut() {
158            if let Some(on_url_change) = &self.on_url_change {
159                let engine_url = self.engine.get_url(*id);
160                if *url != engine_url {
161                    *url = engine_url.clone();
162                    tasks.push(Task::done(on_url_change(*id, engine_url)));
163                }
164            }
165        }
166        for (id, title) in self.titles.iter_mut() {
167            if let Some(on_title_change) = &self.on_title_change {
168                let engine_title = self.engine.get_title(*id);
169                if *title != engine_title {
170                    *title = engine_title.clone();
171                    tasks.push(Task::done(on_title_change(*id, engine_title)));
172                }
173            }
174        }
175
176        match action {
177            Action::CloseView(id) => {
178                self.engine.remove_view(id);
179                self.urls.retain(|url| url.0 != id);
180                self.titles.retain(|title| title.0 != id);
181
182                if let Some(on_view_close) = &self.on_close_view {
183                    tasks.push(Task::done((on_view_close)(id)))
184                }
185            }
186            Action::CreateView(page_type) => {
187                let id = if let PageType::Url(url) = page_type {
188                    if !self.engine.handles_urls() {
189                        let id = self.engine.new_view(self.view_size, None);
190                        self.engine.goto(id, PageType::Url(url.clone()));
191
192                        #[cfg(any(feature = "litehtml", feature = "blitz"))]
193                        if let Some(mapper) = &self.action_mapper {
194                            let mapper = mapper.clone();
195                            let url_clone = url.clone();
196                            tasks.push(Task::perform(
197                                crate::fetch::fetch_html(url),
198                                move |result| mapper(Action::FetchComplete(id, url_clone, result)),
199                            ));
200                        } else {
201                            eprintln!("iced_webview: on_action() mapper required for URL navigation with this engine");
202                        }
203
204                        #[cfg(not(any(feature = "litehtml", feature = "blitz")))]
205                        eprintln!("iced_webview: on_action() mapper required for URL navigation with this engine");
206
207                        id
208                    } else {
209                        self.engine
210                            .new_view(self.view_size, Some(PageType::Url(url)))
211                    }
212                } else {
213                    self.engine.new_view(self.view_size, Some(page_type))
214                };
215
216                self.urls.push((id, String::new()));
217                self.titles.push((id, String::new()));
218
219                if let Some(on_view_create) = &self.on_create_view {
220                    tasks.push(Task::done((on_view_create)(id)))
221                }
222            }
223            Action::GoBackward(id) => {
224                self.engine.go_back(id);
225                self.engine.request_render(id, self.view_size);
226            }
227            Action::GoForward(id) => {
228                self.engine.go_forward(id);
229                self.engine.request_render(id, self.view_size);
230            }
231            Action::GoToUrl(id, url) => {
232                self.inflight_images = 0;
233                let epoch = self.nav_epochs.entry(id).or_insert(0);
234                *epoch = epoch.wrapping_add(1);
235                let url_str = url.to_string();
236                self.engine.goto(id, PageType::Url(url_str.clone()));
237
238                #[cfg(any(feature = "litehtml", feature = "blitz"))]
239                if !self.engine.handles_urls() {
240                    if let Some(mapper) = &self.action_mapper {
241                        let mapper = mapper.clone();
242                        let fetch_url = url_str.clone();
243                        tasks.push(Task::perform(
244                            crate::fetch::fetch_html(fetch_url),
245                            move |result| mapper(Action::FetchComplete(id, url_str, result)),
246                        ));
247                    } else {
248                        eprintln!("iced_webview: on_action() mapper required for URL navigation with this engine");
249                    }
250                }
251
252                #[cfg(not(any(feature = "litehtml", feature = "blitz")))]
253                if !self.engine.handles_urls() {
254                    eprintln!("iced_webview: on_action() mapper required for URL navigation with this engine");
255                }
256
257                self.engine.request_render(id, self.view_size);
258            }
259            Action::Refresh(id) => {
260                self.engine.refresh(id);
261                self.engine.request_render(id, self.view_size);
262            }
263            Action::SendKeyboardEvent(id, event) => {
264                self.engine.handle_keyboard_event(id, event);
265                self.engine.request_render(id, self.view_size);
266            }
267            Action::SendMouseEvent(id, event, point) => {
268                self.engine.handle_mouse_event(id, point, event);
269
270                if let Some(href) = self.engine.take_anchor_click(id) {
271                    let current = self.engine.get_url(id);
272                    let base = Url::parse(&current).ok();
273                    match Url::parse(&href).or_else(|_| {
274                        base.as_ref()
275                            .ok_or(url::ParseError::RelativeUrlWithoutBase)
276                            .and_then(|b| b.join(&href))
277                    }) {
278                        Ok(resolved) => {
279                            let scheme = resolved.scheme();
280                            if scheme == "http" || scheme == "https" {
281                                let is_same_page = base
282                                    .as_ref()
283                                    .is_some_and(|cur| crate::util::is_same_page(&resolved, cur));
284                                if is_same_page {
285                                    if let Some(fragment) = resolved.fragment() {
286                                        self.engine.scroll_to_fragment(id, fragment);
287                                    }
288                                } else {
289                                    tasks.push(self.update(Action::GoToUrl(id, resolved)));
290                                }
291                            }
292                        }
293                        Err(e) => {
294                            eprintln!("iced_webview: failed to resolve anchor URL '{href}': {e}");
295                        }
296                    }
297                }
298
299                return Task::batch(tasks);
300            }
301            Action::Update(id) => {
302                self.engine.update();
303                self.engine.request_render(id, self.view_size);
304
305                if self.inflight_images == 0 {
306                    self.engine.flush_staged_images(id, self.view_size);
307                }
308
309                #[cfg(any(feature = "litehtml", feature = "blitz"))]
310                if let Some(mapper) = &self.action_mapper {
311                    let pending = self.engine.take_pending_images();
312                    for (view_id, src, baseurl, redraw_on_ready) in pending {
313                        let page_url = self.engine.get_url(view_id);
314                        let resolved = crate::util::resolve_url(&src, &baseurl, &page_url);
315                        let resolved = match resolved {
316                            Ok(u) => u,
317                            Err(_) => continue,
318                        };
319                        let scheme = resolved.scheme();
320                        if scheme != "http" && scheme != "https" {
321                            continue;
322                        }
323                        self.inflight_images += 1;
324                        let mapper = mapper.clone();
325                        let raw_src = src.clone();
326                        let epoch = *self.nav_epochs.get(&view_id).unwrap_or(&0);
327                        tasks.push(Task::perform(
328                            crate::fetch::fetch_image(resolved.to_string()),
329                            move |result| {
330                                mapper(Action::ImageFetchComplete(
331                                    view_id,
332                                    raw_src,
333                                    result,
334                                    redraw_on_ready,
335                                    epoch,
336                                ))
337                            },
338                        ));
339                    }
340                }
341
342                return Task::batch(tasks);
343            }
344            Action::UpdateAll => {
345                self.engine.update();
346
347                if self.inflight_images == 0 {
348                    for id in self.engine.view_ids() {
349                        self.engine.flush_staged_images(id, self.view_size);
350                    }
351                }
352
353                self.engine.render(self.view_size);
354
355                #[cfg(any(feature = "litehtml", feature = "blitz"))]
356                if let Some(mapper) = &self.action_mapper {
357                    let pending = self.engine.take_pending_images();
358                    for (view_id, src, baseurl, redraw_on_ready) in pending {
359                        let page_url = self.engine.get_url(view_id);
360                        let resolved = crate::util::resolve_url(&src, &baseurl, &page_url);
361                        let resolved = match resolved {
362                            Ok(u) => u,
363                            Err(_) => continue,
364                        };
365                        let scheme = resolved.scheme();
366                        if scheme != "http" && scheme != "https" {
367                            continue;
368                        }
369                        self.inflight_images += 1;
370                        let mapper = mapper.clone();
371                        let raw_src = src.clone();
372                        let epoch = *self.nav_epochs.get(&view_id).unwrap_or(&0);
373                        tasks.push(Task::perform(
374                            crate::fetch::fetch_image(resolved.to_string()),
375                            move |result| {
376                                mapper(Action::ImageFetchComplete(
377                                    view_id,
378                                    raw_src,
379                                    result,
380                                    redraw_on_ready,
381                                    epoch,
382                                ))
383                            },
384                        ));
385                    }
386                }
387
388                return Task::batch(tasks);
389            }
390            Action::Resize(size) => {
391                if self.view_size != size {
392                    self.view_size = size;
393                    self.engine.resize(size);
394                }
395                // Always skip the per-action render below; the Update/UpdateAll
396                // tick handles it. For no-op resizes (most frames) this avoids
397                // texture churn; for real resizes the next tick picks it up.
398                return Task::batch(tasks);
399            }
400            Action::CopySelection(id) => {
401                if let Some(text) = self.engine.get_selected_text(id) {
402                    if let Some(on_copy) = &self.on_copy {
403                        tasks.push(Task::done((on_copy)(text)));
404                    }
405                }
406                return Task::batch(tasks);
407            }
408            Action::FetchComplete(view_id, url, result) => {
409                if !self.engine.has_view(view_id) {
410                    return Task::batch(tasks);
411                }
412                match result {
413                    Ok((html, css_cache)) => {
414                        self.engine.set_css_cache(view_id, css_cache);
415                        self.engine.goto(view_id, PageType::Html(html));
416                    }
417                    Err(e) => {
418                        let error_html = format!(
419                            "<html><body><h1>Failed to load</h1><p>{}</p><p>{}</p></body></html>",
420                            crate::util::html_escape(&url),
421                            crate::util::html_escape(&e),
422                        );
423                        self.engine.goto(view_id, PageType::Html(error_html));
424                    }
425                }
426                self.engine.request_render(view_id, self.view_size);
427            }
428            Action::ImageFetchComplete(view_id, src, result, redraw_on_ready, epoch) => {
429                self.inflight_images = self.inflight_images.saturating_sub(1);
430                let current_epoch = *self.nav_epochs.get(&view_id).unwrap_or(&0);
431                if epoch != current_epoch {
432                    return Task::batch(tasks);
433                }
434                if self.engine.has_view(view_id) {
435                    match &result {
436                        Ok(bytes) => {
437                            self.engine.load_image_from_bytes(
438                                view_id,
439                                &src,
440                                bytes,
441                                redraw_on_ready,
442                            );
443                        }
444                        Err(e) => {
445                            eprintln!("iced_webview: failed to fetch image '{}': {}", src, e);
446                        }
447                    }
448                }
449                return Task::batch(tasks);
450            }
451        };
452
453        Task::batch(tasks)
454    }
455
456    /// Like a normal `view()` method in iced, but takes an id of the desired view
457    pub fn view<'a, T: 'a>(&'a self, id: usize) -> Element<'a, Action, T> {
458        let content_height = self.engine.get_content_height(id);
459
460        if content_height > 0.0 {
461            WebViewWidget::new(
462                id,
463                self.view_size,
464                self.engine.get_view(id),
465                self.engine.get_cursor(id),
466                self.engine.get_selection_rects(id),
467                self.engine.get_scroll_y(id),
468                content_height,
469            )
470            .into()
471        } else {
472            #[cfg(any(feature = "servo", feature = "cef"))]
473            {
474                shader::Shader::new(AdvancedShaderProgram::new(
475                    id,
476                    self.engine.get_view(id),
477                    self.engine.get_cursor(id),
478                ))
479                .width(Length::Fill)
480                .height(Length::Fill)
481                .into()
482            }
483            #[cfg(not(any(feature = "servo", feature = "cef")))]
484            {
485                WebViewWidget::new(
486                    id,
487                    self.view_size,
488                    self.engine.get_view(id),
489                    self.engine.get_cursor(id),
490                    self.engine.get_selection_rects(id),
491                    0.0,
492                    0.0,
493                )
494                .into()
495            }
496        }
497    }
498}
499
500#[cfg(any(feature = "servo", feature = "cef"))]
501struct AdvancedShaderProgram<'a> {
502    view_id: ViewId,
503    image_info: &'a ImageInfo,
504    cursor: Interaction,
505}
506
507#[cfg(any(feature = "servo", feature = "cef"))]
508impl<'a> AdvancedShaderProgram<'a> {
509    fn new(view_id: ViewId, image_info: &'a ImageInfo, cursor: Interaction) -> Self {
510        Self {
511            view_id,
512            image_info,
513            cursor,
514        }
515    }
516}
517
518#[cfg(any(feature = "servo", feature = "cef"))]
519#[derive(Default)]
520struct AdvancedShaderState {
521    bounds: Size<u32>,
522}
523
524#[cfg(any(feature = "servo", feature = "cef"))]
525impl<'a> shader::Program<Action> for AdvancedShaderProgram<'a> {
526    type State = AdvancedShaderState;
527    type Primitive = WebViewPrimitive;
528
529    fn update(
530        &self,
531        state: &mut Self::State,
532        event: &Event,
533        bounds: Rectangle,
534        cursor: mouse::Cursor,
535    ) -> Option<shader::Action<Action>> {
536        let size = Size::new(bounds.width as u32, bounds.height as u32);
537        if state.bounds != size {
538            state.bounds = size;
539            return Some(shader::Action::publish(Action::Resize(size)));
540        }
541
542        match event {
543            Event::Keyboard(event) => {
544                if let keyboard::Event::KeyPressed {
545                    key: keyboard::Key::Character(c),
546                    modifiers,
547                    ..
548                } = event
549                {
550                    if modifiers.command() && c.as_str() == "c" {
551                        return Some(shader::Action::publish(Action::CopySelection(self.view_id)));
552                    }
553                }
554                Some(shader::Action::publish(Action::SendKeyboardEvent(
555                    self.view_id,
556                    event.clone(),
557                )))
558            }
559            Event::Mouse(event) => {
560                if let Some(point) = cursor.position_in(bounds) {
561                    Some(shader::Action::publish(Action::SendMouseEvent(
562                        self.view_id,
563                        *event,
564                        point,
565                    )))
566                } else if matches!(event, mouse::Event::CursorLeft) {
567                    Some(shader::Action::publish(Action::SendMouseEvent(
568                        self.view_id,
569                        *event,
570                        Point::ORIGIN,
571                    )))
572                } else {
573                    None
574                }
575            }
576            _ => None,
577        }
578    }
579
580    fn draw(
581        &self,
582        _state: &Self::State,
583        _cursor: mouse::Cursor,
584        _bounds: Rectangle,
585    ) -> Self::Primitive {
586        WebViewPrimitive {
587            pixels: self.image_info.pixels(),
588            width: self.image_info.image_width(),
589            height: self.image_info.image_height(),
590        }
591    }
592
593    fn mouse_interaction(
594        &self,
595        _state: &Self::State,
596        _bounds: Rectangle,
597        _cursor: mouse::Cursor,
598    ) -> Interaction {
599        self.cursor
600    }
601}
602
603struct WebViewWidget<'a> {
604    id: ViewId,
605    bounds: Size<u32>,
606    handle: core_image::Handle,
607    cursor: Interaction,
608    selection_rects: &'a [[f32; 4]],
609    scroll_y: f32,
610    content_height: f32,
611}
612
613impl<'a> WebViewWidget<'a> {
614    fn new(
615        id: ViewId,
616        bounds: Size<u32>,
617        image: &ImageInfo,
618        cursor: Interaction,
619        selection_rects: &'a [[f32; 4]],
620        scroll_y: f32,
621        content_height: f32,
622    ) -> Self {
623        Self {
624            id,
625            bounds,
626            handle: image.as_handle(),
627            cursor,
628            selection_rects,
629            scroll_y,
630            content_height,
631        }
632    }
633}
634
635impl<'a, Renderer, Theme> Widget<Action, Theme, Renderer> for WebViewWidget<'a>
636where
637    Renderer: iced::advanced::Renderer
638        + iced::advanced::image::Renderer<Handle = iced::advanced::image::Handle>,
639{
640    fn size(&self) -> Size<Length> {
641        Size {
642            width: Length::Fill,
643            height: Length::Fill,
644        }
645    }
646
647    fn layout(
648        &mut self,
649        _tree: &mut Tree,
650        _renderer: &Renderer,
651        limits: &layout::Limits,
652    ) -> layout::Node {
653        layout::Node::new(limits.max())
654    }
655
656    fn draw(
657        &self,
658        _tree: &Tree,
659        renderer: &mut Renderer,
660        _theme: &Theme,
661        _style: &renderer::Style,
662        layout: Layout<'_>,
663        _cursor: mouse::Cursor,
664        viewport: &Rectangle,
665    ) {
666        let bounds = layout.bounds();
667
668        if self.content_height > 0.0 {
669            renderer.with_layer(bounds, |renderer| {
670                let image_bounds = Rectangle {
671                    x: bounds.x,
672                    y: bounds.y - self.scroll_y,
673                    width: bounds.width,
674                    height: self.content_height,
675                };
676                renderer.draw_image(
677                    core_image::Image::new(self.handle.clone()).snap(true),
678                    image_bounds,
679                    *viewport,
680                );
681            });
682        } else {
683            renderer.draw_image(
684                core_image::Image::new(self.handle.clone()).snap(true),
685                bounds,
686                *viewport,
687            );
688        }
689
690        if !self.selection_rects.is_empty() {
691            let rects = self.selection_rects;
692            let scroll_y = self.scroll_y;
693            renderer.with_layer(bounds, |renderer| {
694                let highlight = iced::Color::from_rgba(0.26, 0.52, 0.96, 0.3);
695                for rect in rects {
696                    let quad_bounds = Rectangle {
697                        x: bounds.x + rect[0],
698                        y: bounds.y + rect[1] - scroll_y,
699                        width: rect[2],
700                        height: rect[3],
701                    };
702                    renderer.fill_quad(
703                        renderer::Quad {
704                            bounds: quad_bounds,
705                            ..renderer::Quad::default()
706                        },
707                        highlight,
708                    );
709                }
710            });
711        }
712    }
713
714    fn update(
715        &mut self,
716        _state: &mut Tree,
717        event: &Event,
718        layout: Layout<'_>,
719        cursor: mouse::Cursor,
720        _renderer: &Renderer,
721        _clipboard: &mut dyn Clipboard,
722        shell: &mut Shell<'_, Action>,
723        _viewport: &Rectangle,
724    ) {
725        let size = Size::new(layout.bounds().width as u32, layout.bounds().height as u32);
726        if self.bounds != size {
727            shell.publish(Action::Resize(size));
728        }
729
730        match event {
731            Event::Keyboard(event) => {
732                if let keyboard::Event::KeyPressed {
733                    key: keyboard::Key::Character(c),
734                    modifiers,
735                    ..
736                } = event
737                {
738                    if modifiers.command() && c.as_str() == "c" {
739                        shell.publish(Action::CopySelection(self.id));
740                    }
741                }
742                shell.publish(Action::SendKeyboardEvent(self.id, event.clone()));
743            }
744            Event::Mouse(event) => {
745                if let Some(point) = cursor.position_in(layout.bounds()) {
746                    shell.publish(Action::SendMouseEvent(self.id, *event, point));
747                } else if matches!(event, mouse::Event::CursorLeft) {
748                    shell.publish(Action::SendMouseEvent(self.id, *event, Point::ORIGIN));
749                }
750            }
751            _ => (),
752        }
753    }
754
755    fn mouse_interaction(
756        &self,
757        _state: &Tree,
758        layout: Layout<'_>,
759        cursor: mouse::Cursor,
760        _viewport: &Rectangle,
761        _renderer: &Renderer,
762    ) -> mouse::Interaction {
763        if cursor.is_over(layout.bounds()) {
764            self.cursor
765        } else {
766            mouse::Interaction::Idle
767        }
768    }
769}
770
771impl<'a, Message: 'a, Renderer, Theme> From<WebViewWidget<'a>>
772    for Element<'a, Message, Theme, Renderer>
773where
774    Renderer: advanced::Renderer + advanced::image::Renderer<Handle = advanced::image::Handle>,
775    WebViewWidget<'a>: Widget<Message, Theme, Renderer>,
776{
777    fn from(widget: WebViewWidget<'a>) -> Self {
778        Self::new(widget)
779    }
780}