servo/
webview.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::{Ref, RefCell, RefMut};
6use std::hash::Hash;
7use std::rc::{Rc, Weak};
8use std::time::Duration;
9
10use accesskit::{
11    Node as AccesskitNode, NodeId, Role, Tree, TreeId, TreeUpdate, Uuid as AccesskitUuid,
12};
13use dpi::PhysicalSize;
14use embedder_traits::{
15    ContextMenuAction, ContextMenuItem, Cursor, EmbedderControlId, EmbedderControlRequest, Image,
16    InputEvent, InputEventAndId, InputEventId, JSValue, JavaScriptEvaluationError, LoadStatus,
17    MediaSessionActionType, NewWebViewDetails, ScreenGeometry, ScreenshotCaptureError, Scroll,
18    Theme, TraversalId, ViewportDetails, WebViewPoint, WebViewRect,
19};
20use euclid::{Scale, Size2D};
21use image::RgbaImage;
22use paint_api::WebViewTrait;
23use paint_api::rendering_context::RenderingContext;
24use servo_base::generic_channel::GenericSender;
25use servo_base::id::WebViewId;
26use servo_config::pref;
27use servo_constellation_traits::{EmbedderToConstellationMessage, TraversalDirection};
28use servo_geometry::DeviceIndependentPixel;
29use servo_url::ServoUrl;
30use style_traits::CSSPixel;
31use url::Url;
32use webrender_api::units::{DeviceIntRect, DevicePixel, DevicePoint, DeviceSize};
33
34use crate::clipboard_delegate::{ClipboardDelegate, DefaultClipboardDelegate};
35#[cfg(feature = "gamepad")]
36use crate::gamepad_delegate::{DefaultGamepadDelegate, GamepadDelegate};
37use crate::responders::IpcResponder;
38use crate::servo::PendingHandledInputEvent;
39use crate::webview_delegate::{CreateNewWebViewRequest, DefaultWebViewDelegate, WebViewDelegate};
40use crate::{
41    ColorPicker, ContextMenu, EmbedderControl, InputMethodControl, SelectElement, Servo,
42    UserContentManager, WebRenderDebugOption,
43};
44
45pub(crate) const MINIMUM_WEBVIEW_SIZE: Size2D<i32, DevicePixel> = Size2D::new(1, 1);
46
47/// A handle to a Servo webview. If you clone this handle, it does not create a new webview,
48/// but instead creates a new handle to the webview. Once the last handle is dropped, Servo
49/// considers that the webview has closed and will clean up all associated resources related
50/// to this webview.
51///
52/// ## Creating a WebView
53///
54/// To create a [`WebView`], use [`WebViewBuilder`].
55///
56/// ## Rendering Model
57///
58/// Every [`WebView`] has a [`RenderingContext`](crate::RenderingContext). The embedder manages when
59/// the contents of the [`WebView`] paint to the [`RenderingContext`](crate::RenderingContext). When
60/// a [`WebView`] needs to be painted, for instance, because its contents have changed, Servo will
61/// call [`WebViewDelegate::notify_new_frame_ready`] in order to signal that it is time to repaint
62/// the [`WebView`] using [`WebView::paint`].
63///
64/// An example of how this flow might work is:
65///
66/// 1. [`WebViewDelegate::notify_new_frame_ready`] is called. The applications triggers a request
67///    to repaint the window that contains this [`WebView`].
68/// 2. During window repainting, the application calls [`WebView::paint`] and the contents of the
69///    [`RenderingContext`][crate::RenderingContext] are updated.
70/// 3. If the [`RenderingContext`][crate::RenderingContext] is double-buffered, the
71///    application then calls [`crate::RenderingContext::present()`] in order to swap the back buffer
72///    to the front, finally displaying the updated [`WebView`] contents.
73///
74/// In cases where the [`WebView`] contents have not been updated, but a repaint is necessary, for
75/// instance when repainting a window due to damage, an application may simply perform the final two
76/// steps and Servo will repaint even without first calling the
77/// [`WebViewDelegate::notify_new_frame_ready`] method.
78#[derive(Clone)]
79pub struct WebView(Rc<RefCell<WebViewInner>>);
80
81impl PartialEq for WebView {
82    fn eq(&self, other: &Self) -> bool {
83        self.inner().id == other.inner().id
84    }
85}
86
87impl Hash for WebView {
88    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
89        self.inner().id.hash(state);
90    }
91}
92
93pub(crate) struct WebViewInner {
94    pub(crate) id: WebViewId,
95    pub(crate) servo: Servo,
96    pub(crate) delegate: Rc<dyn WebViewDelegate>,
97    pub(crate) clipboard_delegate: Rc<dyn ClipboardDelegate>,
98    #[cfg(feature = "gamepad")]
99    pub(crate) gamepad_delegate: Rc<dyn GamepadDelegate>,
100
101    /// AccessKit subtree id for this [`WebView`], if accessibility is active.
102    ///
103    /// Set by [`WebView::set_accessibility_active()`], and forwarded to the constellation via
104    /// [`EmbedderToConstellationMessage::SetAccessibilityActive`].
105    pub(crate) accesskit_tree_id: Option<TreeId>,
106    /// [`TreeId`] of the web contents of this [`WebView`]’s active top-level pipeline,
107    /// which is grafted into the tree for this [`WebView`].
108    pub(crate) grafted_accesskit_tree_id: Option<TreeId>,
109
110    rendering_context: Rc<dyn RenderingContext>,
111    user_content_manager: Option<Rc<UserContentManager>>,
112    hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
113    load_status: LoadStatus,
114    status_text: Option<String>,
115    page_title: Option<String>,
116    favicon: Option<Image>,
117    focused: bool,
118    animating: bool,
119    cursor: Cursor,
120
121    /// The back / forward list of this WebView.
122    back_forward_list: Vec<Url>,
123
124    /// The current index in the back / forward list.
125    back_forward_list_index: usize,
126}
127
128impl Drop for WebViewInner {
129    fn drop(&mut self) {
130        self.servo
131            .constellation_proxy()
132            .send(EmbedderToConstellationMessage::CloseWebView(self.id));
133        self.servo.paint_mut().remove_webview(self.id);
134    }
135}
136
137impl WebView {
138    pub(crate) fn new(mut builder: WebViewBuilder) -> Self {
139        let servo = builder.servo;
140        let painter_id = servo
141            .paint_mut()
142            .register_rendering_context(builder.rendering_context.clone());
143
144        let id = WebViewId::new(painter_id);
145        let webview = Self(Rc::new(RefCell::new(WebViewInner {
146            id,
147            servo: servo.clone(),
148            rendering_context: builder.rendering_context,
149            delegate: builder.delegate,
150            clipboard_delegate: builder
151                .clipboard_delegate
152                .unwrap_or_else(|| Rc::new(DefaultClipboardDelegate)),
153            #[cfg(feature = "gamepad")]
154            gamepad_delegate: builder
155                .gamepad_delegate
156                .unwrap_or_else(|| Rc::new(DefaultGamepadDelegate)),
157            accesskit_tree_id: None,
158            grafted_accesskit_tree_id: None,
159            hidpi_scale_factor: builder.hidpi_scale_factor,
160            load_status: LoadStatus::Started,
161            status_text: None,
162            page_title: None,
163            favicon: None,
164            focused: false,
165            animating: false,
166            cursor: Cursor::Pointer,
167            back_forward_list: Default::default(),
168            back_forward_list_index: 0,
169            user_content_manager: builder.user_content_manager.clone(),
170        })));
171
172        let viewport_details = webview.viewport_details();
173        servo.paint().add_webview(
174            Box::new(ServoRendererWebView {
175                weak_handle: webview.weak_handle(),
176                id,
177            }),
178            viewport_details,
179        );
180
181        servo
182            .webviews_mut()
183            .insert(webview.id(), webview.weak_handle());
184
185        let user_content_manager_id = builder
186            .user_content_manager
187            .as_ref()
188            .map(|user_content_manager| user_content_manager.id());
189
190        let new_webview_details = NewWebViewDetails {
191            webview_id: webview.id(),
192            viewport_details,
193            user_content_manager_id,
194        };
195
196        // There are two possibilities here. Either the WebView is a new toplevel
197        // WebView in which case `Self::create_new_webview_responder` is `None` or this
198        // is the response to a `WebViewDelegate::request_create_new` method in which
199        // case script expects that we just return the information directly back to
200        // the `ScriptThread`.
201        match builder.create_new_webview_responder.as_mut() {
202            Some(responder) => {
203                let _ = responder.send(Some(new_webview_details));
204            },
205            None => {
206                let url = builder.url.unwrap_or(
207                    Url::parse("about:blank")
208                        .expect("Should always be able to parse 'about:blank'."),
209                );
210
211                servo
212                    .constellation_proxy()
213                    .send(EmbedderToConstellationMessage::NewWebView(
214                        url.into(),
215                        new_webview_details,
216                    ));
217            },
218        }
219
220        webview
221    }
222
223    fn inner(&self) -> Ref<'_, WebViewInner> {
224        self.0.borrow()
225    }
226
227    fn inner_mut(&self) -> RefMut<'_, WebViewInner> {
228        self.0.borrow_mut()
229    }
230
231    pub(crate) fn request_create_new(
232        &self,
233        response_sender: GenericSender<Option<NewWebViewDetails>>,
234    ) {
235        let request = CreateNewWebViewRequest {
236            servo: self.inner().servo.clone(),
237            responder: IpcResponder::new(response_sender, None),
238        };
239        self.delegate().request_create_new(self.clone(), request);
240    }
241
242    pub(crate) fn viewport_details(&self) -> ViewportDetails {
243        // The division by 1 represents the page's default zoom of 100%,
244        // and gives us the appropriate CSSPixel type for the viewport.
245        let inner = self.inner();
246        let scaled_viewport_size =
247            inner.rendering_context.size2d().to_f32() / inner.hidpi_scale_factor;
248        ViewportDetails {
249            size: scaled_viewport_size / Scale::new(1.0),
250            hidpi_scale_factor: Scale::new(inner.hidpi_scale_factor.0),
251        }
252    }
253
254    pub(crate) fn from_weak_handle(inner: &Weak<RefCell<WebViewInner>>) -> Option<Self> {
255        inner.upgrade().map(WebView)
256    }
257
258    pub(crate) fn weak_handle(&self) -> Weak<RefCell<WebViewInner>> {
259        Rc::downgrade(&self.0)
260    }
261
262    pub fn delegate(&self) -> Rc<dyn WebViewDelegate> {
263        self.inner().delegate.clone()
264    }
265
266    pub fn clipboard_delegate(&self) -> Rc<dyn ClipboardDelegate> {
267        self.inner().clipboard_delegate.clone()
268    }
269
270    #[cfg(feature = "gamepad")]
271    pub fn gamepad_delegate(&self) -> Rc<dyn GamepadDelegate> {
272        self.inner().gamepad_delegate.clone()
273    }
274
275    pub fn id(&self) -> WebViewId {
276        self.inner().id
277    }
278
279    pub fn load_status(&self) -> LoadStatus {
280        self.inner().load_status
281    }
282
283    pub(crate) fn set_load_status(self, new_value: LoadStatus) {
284        if self.inner().load_status == new_value {
285            return;
286        }
287        self.inner_mut().load_status = new_value;
288        self.delegate().notify_load_status_changed(self, new_value);
289    }
290
291    pub fn url(&self) -> Option<Url> {
292        let inner = self.inner();
293        inner
294            .back_forward_list
295            .get(inner.back_forward_list_index)
296            .cloned()
297    }
298
299    pub fn status_text(&self) -> Option<String> {
300        self.inner().status_text.clone()
301    }
302
303    pub(crate) fn set_status_text(self, new_value: Option<String>) {
304        if self.inner().status_text == new_value {
305            return;
306        }
307        self.inner_mut().status_text = new_value.clone();
308        self.delegate().notify_status_text_changed(self, new_value);
309    }
310
311    pub fn page_title(&self) -> Option<String> {
312        self.inner().page_title.clone()
313    }
314
315    pub(crate) fn set_page_title(self, new_value: Option<String>) {
316        if self.inner().page_title == new_value {
317            return;
318        }
319        self.inner_mut().page_title = new_value.clone();
320        self.delegate().notify_page_title_changed(self, new_value);
321    }
322
323    pub fn favicon(&self) -> Option<Ref<'_, Image>> {
324        Ref::filter_map(self.inner(), |inner| inner.favicon.as_ref()).ok()
325    }
326
327    pub(crate) fn set_favicon(self, new_value: Image) {
328        self.inner_mut().favicon = Some(new_value);
329        self.delegate().notify_favicon_changed(self);
330    }
331
332    pub fn focused(&self) -> bool {
333        self.inner().focused
334    }
335
336    pub(crate) fn set_focused(self, new_value: bool) {
337        if self.inner().focused == new_value {
338            return;
339        }
340        self.inner_mut().focused = new_value;
341        self.delegate().notify_focus_changed(self, new_value);
342    }
343
344    pub fn cursor(&self) -> Cursor {
345        self.inner().cursor
346    }
347
348    pub(crate) fn set_cursor(self, new_value: Cursor) {
349        if self.inner().cursor == new_value {
350            return;
351        }
352        self.inner_mut().cursor = new_value;
353        self.delegate().notify_cursor_changed(self, new_value);
354    }
355
356    pub fn focus(&self) {
357        self.inner()
358            .servo
359            .constellation_proxy()
360            .send(EmbedderToConstellationMessage::FocusWebView(self.id()));
361    }
362
363    pub fn blur(&self) {
364        self.inner()
365            .servo
366            .constellation_proxy()
367            .send(EmbedderToConstellationMessage::BlurWebView);
368    }
369
370    /// Whether or not this [`WebView`] has animating content, such as a CSS animation or
371    /// transition or is running `requestAnimationFrame` callbacks. This indicates that the
372    /// embedding application should be spinning the Servo event loop on regular intervals
373    /// in order to trigger animation updates.
374    pub fn animating(self) -> bool {
375        self.inner().animating
376    }
377
378    pub(crate) fn set_animating(self, new_value: bool) {
379        if self.inner().animating == new_value {
380            return;
381        }
382        self.inner_mut().animating = new_value;
383        self.delegate().notify_animating_changed(self, new_value);
384    }
385
386    /// The size of this [`WebView`]'s [`RenderingContext`].
387    pub fn size(&self) -> DeviceSize {
388        self.inner().rendering_context.size2d().to_f32()
389    }
390
391    /// Request that the given [`WebView`]'s [`RenderingContext`] be resized. Note that the
392    /// minimum size for a WebView is 1 pixel by 1 pixel so any requested size will be
393    /// clamped by that value.
394    ///
395    /// This will also resize any other [`WebView`] using the same [`RenderingContext`]. A
396    /// [`WebView`] is always as big as its [`RenderingContext`].
397    pub fn resize(&self, new_size: PhysicalSize<u32>) {
398        let new_size = PhysicalSize {
399            width: new_size.width.max(MINIMUM_WEBVIEW_SIZE.width as u32),
400            height: new_size.height.max(MINIMUM_WEBVIEW_SIZE.height as u32),
401        };
402
403        self.inner()
404            .servo
405            .paint()
406            .resize_rendering_context(self.id(), new_size);
407    }
408
409    pub fn hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
410        self.inner().hidpi_scale_factor
411    }
412
413    pub fn set_hidpi_scale_factor(
414        &self,
415        new_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
416    ) {
417        if self.inner().hidpi_scale_factor == new_scale_factor {
418            return;
419        }
420
421        self.inner_mut().hidpi_scale_factor = new_scale_factor;
422        self.inner()
423            .servo
424            .paint()
425            .set_hidpi_scale_factor(self.id(), new_scale_factor);
426    }
427
428    pub fn show(&self) {
429        self.inner()
430            .servo
431            .paint()
432            .show_webview(self.id())
433            .expect("BUG: invalid WebView instance");
434    }
435
436    pub fn hide(&self) {
437        self.inner()
438            .servo
439            .paint()
440            .hide_webview(self.id())
441            .expect("BUG: invalid WebView instance");
442    }
443
444    pub fn notify_theme_change(&self, theme: Theme) {
445        self.inner()
446            .servo
447            .constellation_proxy()
448            .send(EmbedderToConstellationMessage::ThemeChange(
449                self.id(),
450                theme,
451            ))
452    }
453
454    pub fn load(&self, url: Url) {
455        self.inner()
456            .servo
457            .constellation_proxy()
458            .send(EmbedderToConstellationMessage::LoadUrl(
459                self.id(),
460                url.into(),
461            ))
462    }
463
464    pub fn reload(&self) {
465        self.inner_mut().load_status = LoadStatus::Started;
466        self.inner()
467            .servo
468            .constellation_proxy()
469            .send(EmbedderToConstellationMessage::Reload(self.id()))
470    }
471
472    pub fn can_go_back(&self) -> bool {
473        self.inner().back_forward_list_index != 0
474    }
475
476    pub fn go_back(&self, amount: usize) -> TraversalId {
477        let traversal_id = TraversalId::new();
478        self.inner().servo.constellation_proxy().send(
479            EmbedderToConstellationMessage::TraverseHistory(
480                self.id(),
481                TraversalDirection::Back(amount),
482                traversal_id.clone(),
483            ),
484        );
485        traversal_id
486    }
487
488    pub fn can_go_forward(&self) -> bool {
489        let inner = self.inner();
490        inner.back_forward_list.len() > inner.back_forward_list_index + 1
491    }
492
493    pub fn go_forward(&self, amount: usize) -> TraversalId {
494        let traversal_id = TraversalId::new();
495        self.inner().servo.constellation_proxy().send(
496            EmbedderToConstellationMessage::TraverseHistory(
497                self.id(),
498                TraversalDirection::Forward(amount),
499                traversal_id.clone(),
500            ),
501        );
502        traversal_id
503    }
504
505    /// Ask the [`WebView`] to scroll web content. Note that positive scroll offsets reveal more
506    /// content on the bottom and right of the page.
507    pub fn notify_scroll_event(&self, scroll: Scroll, point: WebViewPoint) {
508        self.inner()
509            .servo
510            .paint()
511            .notify_scroll_event(self.id(), scroll, point);
512    }
513
514    pub fn notify_input_event(&self, event: InputEvent) -> InputEventId {
515        let event: InputEventAndId = event.into();
516        let event_id = event.id;
517        let webview_id = self.id();
518        let servo = &self.inner().servo;
519        // Events with a `point` first go to `Paint` for hit testing.
520        if event.event.point().is_some() {
521            if !servo.paint().notify_input_event(self.id(), event) {
522                servo.add_pending_handled_input_event(PendingHandledInputEvent {
523                    event_id,
524                    webview_id,
525                });
526                servo.event_loop_waker().wake();
527            }
528        } else {
529            servo
530                .constellation_proxy()
531                .send(EmbedderToConstellationMessage::ForwardInputEvent(
532                    webview_id, event, None, /* hit_test */
533                ));
534        }
535
536        event_id
537    }
538
539    pub fn notify_media_session_action_event(&self, event: MediaSessionActionType) {
540        self.inner()
541            .servo
542            .constellation_proxy()
543            .send(EmbedderToConstellationMessage::MediaSessionAction(event));
544    }
545
546    /// Set the page zoom of the [`WebView`]. This sets the final page zoom value of the
547    /// [`WebView`]. Unlike [`WebView::pinch_zoom`] *it is not* multiplied by the current
548    /// page zoom value, but overrides it.
549    ///
550    /// [`WebView`]s have two types of zoom, pinch zoom and page zoom. This adjusts page
551    /// zoom, which will adjust the `devicePixelRatio` of the page and cause it to modify
552    /// its layout.
553    ///
554    /// These values will be clamped internally. The values used for clamping can be
555    /// adjusted by page content when `<meta viewport>` parsing is enabled via
556    /// `Prefs::viewport_meta_enabled`.
557    pub fn set_page_zoom(&self, new_zoom: f32) {
558        self.inner()
559            .servo
560            .paint()
561            .set_page_zoom(self.id(), new_zoom);
562    }
563
564    /// Get the page zoom of the [`WebView`].
565    pub fn page_zoom(&self) -> f32 {
566        self.inner().servo.paint().page_zoom(self.id())
567    }
568
569    /// Adjust the pinch zoom on this [`WebView`] multiplying the current pinch zoom
570    /// level with the provided `pinch_zoom_delta`.
571    ///
572    /// [`WebView`]s have two types of zoom, pinch zoom and page zoom. This adjusts pinch
573    /// zoom, which is a type of zoom which does not modify layout, and instead simply
574    /// magnifies the view in the viewport.
575    ///
576    /// The final pinch zoom values will be clamped to reasonable defaults (currently to
577    /// the inclusive range [1.0, 10.0]).
578    pub fn adjust_pinch_zoom(&self, pinch_zoom_delta: f32, center: DevicePoint) {
579        self.inner()
580            .servo
581            .paint()
582            .adjust_pinch_zoom(self.id(), pinch_zoom_delta, center);
583    }
584
585    /// Get the pinch zoom of the [`WebView`].
586    pub fn pinch_zoom(&self) -> f32 {
587        self.inner().servo.paint().pinch_zoom(self.id())
588    }
589
590    pub fn device_pixels_per_css_pixel(&self) -> Scale<f32, CSSPixel, DevicePixel> {
591        self.inner()
592            .servo
593            .paint()
594            .device_pixels_per_page_pixel(self.id())
595    }
596
597    pub fn exit_fullscreen(&self) {
598        self.inner()
599            .servo
600            .constellation_proxy()
601            .send(EmbedderToConstellationMessage::ExitFullScreen(self.id()));
602    }
603
604    pub fn set_throttled(&self, throttled: bool) {
605        self.inner().servo.constellation_proxy().send(
606            EmbedderToConstellationMessage::SetWebViewThrottled(self.id(), throttled),
607        );
608    }
609
610    pub fn toggle_webrender_debugging(&self, debugging: WebRenderDebugOption) {
611        self.inner().servo.paint().toggle_webrender_debug(debugging);
612    }
613
614    pub fn capture_webrender(&self) {
615        self.inner().servo.paint().capture_webrender(self.id());
616    }
617
618    pub fn toggle_sampling_profiler(&self, rate: Duration, max_duration: Duration) {
619        self.inner().servo.constellation_proxy().send(
620            EmbedderToConstellationMessage::ToggleProfiler(rate, max_duration),
621        );
622    }
623
624    pub fn send_error(&self, message: String) {
625        self.inner()
626            .servo
627            .constellation_proxy()
628            .send(EmbedderToConstellationMessage::SendError(
629                Some(self.id()),
630                message,
631            ));
632    }
633
634    /// Paint the contents of this [`WebView`] into its `RenderingContext`.
635    pub fn paint(&self) {
636        self.inner().servo.paint().render(self.id());
637    }
638
639    /// Get the [`UserContentManager`] associated with this [`WebView`].
640    pub fn user_content_manager(&self) -> Option<Rc<UserContentManager>> {
641        self.inner().user_content_manager.clone()
642    }
643
644    /// Evaluate the specified string of JavaScript code. Once execution is complete or an error
645    /// occurs, Servo will call `callback`.
646    pub fn evaluate_javascript<T: ToString>(
647        &self,
648        script: T,
649        callback: impl FnOnce(Result<JSValue, JavaScriptEvaluationError>) + 'static,
650    ) {
651        self.inner().servo.javascript_evaluator_mut().evaluate(
652            self.id(),
653            script.to_string(),
654            Box::new(callback),
655        );
656    }
657
658    /// Asynchronously take a screenshot of the [`WebView`] contents, given a `rect` or the whole
659    /// viewport, if no `rect` is given.
660    ///
661    /// This method will wait until the [`WebView`] is ready before the screenshot is taken.
662    /// This includes waiting for:
663    ///
664    ///  - all frames to fire their `load` event.
665    ///  - all render blocking elements, such as stylesheets included via the `<link>`
666    ///    element, to stop blocking the rendering.
667    ///  - all images to be loaded and displayed.
668    ///  - all web fonts are loaded.
669    ///  - the `reftest-wait` and `test-wait` classes have been removed from the root element.
670    ///  - the rendering is up-to-date
671    ///
672    /// Once all these conditions are met and the rendering does not have any pending frames
673    /// to render, the provided `callback` will be called with the results of the screenshot
674    /// operation.
675    pub fn take_screenshot(
676        &self,
677        rect: Option<WebViewRect>,
678        callback: impl FnOnce(Result<RgbaImage, ScreenshotCaptureError>) + 'static,
679    ) {
680        self.inner()
681            .servo
682            .paint()
683            .request_screenshot(self.id(), rect, Box::new(callback));
684    }
685
686    pub(crate) fn set_history(self, new_back_forward_list: Vec<ServoUrl>, new_index: usize) {
687        {
688            let mut inner_mut = self.inner_mut();
689            inner_mut.back_forward_list_index = new_index;
690            inner_mut.back_forward_list = new_back_forward_list
691                .into_iter()
692                .map(ServoUrl::into_url)
693                .collect();
694        }
695
696        let back_forward_list = self.inner().back_forward_list.clone();
697        let back_forward_list_index = self.inner().back_forward_list_index;
698        self.delegate().notify_url_changed(
699            self.clone(),
700            back_forward_list[back_forward_list_index].clone(),
701        );
702        self.delegate().notify_history_changed(
703            self.clone(),
704            back_forward_list,
705            back_forward_list_index,
706        );
707    }
708
709    pub(crate) fn show_embedder_control(
710        self,
711        control_id: EmbedderControlId,
712        position: DeviceIntRect,
713        embedder_control_request: EmbedderControlRequest,
714    ) {
715        let constellation_proxy = self.inner().servo.constellation_proxy().clone();
716        let embedder_control = match embedder_control_request {
717            EmbedderControlRequest::SelectElement(options, selected_option) => {
718                EmbedderControl::SelectElement(SelectElement {
719                    id: control_id,
720                    options,
721                    selected_option,
722                    position,
723                    constellation_proxy,
724                    response_sent: false,
725                })
726            },
727            EmbedderControlRequest::ColorPicker(current_color) => {
728                EmbedderControl::ColorPicker(ColorPicker {
729                    id: control_id,
730                    current_color: Some(current_color),
731                    position,
732                    constellation_proxy,
733                    response_sent: false,
734                })
735            },
736            EmbedderControlRequest::InputMethod(input_method_request) => {
737                EmbedderControl::InputMethod(InputMethodControl {
738                    id: control_id,
739                    input_method_type: input_method_request.input_method_type,
740                    text: input_method_request.text,
741                    insertion_point: input_method_request.insertion_point,
742                    position,
743                    multiline: input_method_request.multiline,
744                    allow_virtual_keyboard: input_method_request.allow_virtual_keyboard,
745                })
746            },
747            EmbedderControlRequest::ContextMenu(mut context_menu_request) => {
748                for item in context_menu_request.items.iter_mut() {
749                    match item {
750                        ContextMenuItem::Item {
751                            action: ContextMenuAction::GoBack,
752                            enabled,
753                            ..
754                        } => *enabled = self.can_go_back(),
755                        ContextMenuItem::Item {
756                            action: ContextMenuAction::GoForward,
757                            enabled,
758                            ..
759                        } => *enabled = self.can_go_forward(),
760                        _ => {},
761                    }
762                }
763                EmbedderControl::ContextMenu(ContextMenu {
764                    id: control_id,
765                    position,
766                    items: context_menu_request.items,
767                    element_info: context_menu_request.element_info,
768                    constellation_proxy,
769                    response_sent: false,
770                })
771            },
772            EmbedderControlRequest::FilePicker { .. } => {
773                unreachable!("This message should be routed through the FileManagerThread")
774            },
775        };
776
777        self.delegate()
778            .show_embedder_control(self.clone(), embedder_control);
779    }
780
781    /// AccessKit subtree id for this [`WebView`], if accessibility is active.
782    pub fn accesskit_tree_id(&self) -> Option<TreeId> {
783        self.inner().accesskit_tree_id
784    }
785
786    /// Activate or deactivate accessibility features for this [`WebView`], returning the
787    /// AccessKit subtree id if accessibility is now active.
788    ///
789    /// After accessibility is activated, you must [graft] (with [`set_tree_id()`]) the returned
790    /// [`TreeId`] into your application’s main AccessKit tree as soon as possible, *before*
791    /// sending any tree updates from the webview to your AccessKit adapter. Otherwise you may
792    /// violate AccessKit’s subtree invariants and **panic**.
793    ///
794    /// If your impl for [`WebViewDelegate::notify_accessibility_tree_update()`] can’t create the
795    /// graft node (and send *that* update to AccessKit) before sending any updates from this
796    /// webview to AccessKit, then it must queue those updates until it can guarantee that.
797    ///
798    /// [graft]: https://docs.rs/accesskit/0.24.0/accesskit/struct.Node.html#method.tree_id
799    /// [`set_tree_id()`]: https://docs.rs/accesskit/0.24.0/accesskit/struct.Node.html#method.set_tree_id
800    pub fn set_accessibility_active(&self, active: bool) -> Option<TreeId> {
801        if !pref!(accessibility_enabled) {
802            return None;
803        }
804
805        if active == self.inner().accesskit_tree_id.is_some() {
806            return self.accesskit_tree_id();
807        }
808
809        if active {
810            let accesskit_tree_id = TreeId(AccesskitUuid::new_v4());
811            self.inner_mut().accesskit_tree_id = Some(accesskit_tree_id);
812        } else {
813            self.inner_mut().accesskit_tree_id = None;
814        }
815
816        self.inner().servo.constellation_proxy().send(
817            EmbedderToConstellationMessage::SetAccessibilityActive(self.id(), active),
818        );
819
820        self.accesskit_tree_id()
821    }
822
823    pub(crate) fn notify_document_accessibility_tree_id(&self, grafted_tree_id: TreeId) {
824        let Some(webview_accesskit_tree_id) = self.inner().accesskit_tree_id else {
825            return;
826        };
827        let old_grafted_tree_id = self
828            .inner_mut()
829            .grafted_accesskit_tree_id
830            .replace(grafted_tree_id);
831        // TODO(accessibility): try to avoid duplicate notifications in the first place?
832        if old_grafted_tree_id == Some(grafted_tree_id) {
833            return;
834        }
835        let root_node_id = NodeId(0);
836        let mut root_node = AccesskitNode::new(Role::ScrollView);
837        let graft_node_id = NodeId(1);
838        let mut graft_node = AccesskitNode::new(Role::GenericContainer);
839        graft_node.set_tree_id(grafted_tree_id);
840        root_node.set_children(vec![graft_node_id]);
841        self.delegate().notify_accessibility_tree_update(
842            self.clone(),
843            TreeUpdate {
844                nodes: vec![(root_node_id, root_node), (graft_node_id, graft_node)],
845                tree: Some(Tree {
846                    root: root_node_id,
847                    toolkit_name: None,
848                    toolkit_version: None,
849                }),
850                tree_id: webview_accesskit_tree_id,
851                focus: root_node_id,
852            },
853        );
854    }
855}
856
857/// A structure used to expose a view of the [`WebView`] to the Servo
858/// renderer, without having the Servo renderer depend on the embedding layer.
859struct ServoRendererWebView {
860    id: WebViewId,
861    weak_handle: Weak<RefCell<WebViewInner>>,
862}
863
864impl WebViewTrait for ServoRendererWebView {
865    fn id(&self) -> WebViewId {
866        self.id
867    }
868
869    fn screen_geometry(&self) -> Option<ScreenGeometry> {
870        let webview = WebView::from_weak_handle(&self.weak_handle)?;
871        webview.delegate().screen_geometry(webview)
872    }
873
874    fn set_animating(&self, new_value: bool) {
875        if let Some(webview) = WebView::from_weak_handle(&self.weak_handle) {
876            webview.set_animating(new_value);
877        }
878    }
879}
880
881/// Builder for creating a [`WebView`].
882pub struct WebViewBuilder {
883    servo: Servo,
884    rendering_context: Rc<dyn RenderingContext>,
885    delegate: Rc<dyn WebViewDelegate>,
886    url: Option<Url>,
887    hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
888    create_new_webview_responder: Option<IpcResponder<Option<NewWebViewDetails>>>,
889    user_content_manager: Option<Rc<UserContentManager>>,
890    clipboard_delegate: Option<Rc<dyn ClipboardDelegate>>,
891    #[cfg(feature = "gamepad")]
892    gamepad_delegate: Option<Rc<dyn GamepadDelegate>>,
893}
894
895impl WebViewBuilder {
896    pub fn new(servo: &Servo, rendering_context: Rc<dyn RenderingContext>) -> Self {
897        Self {
898            servo: servo.clone(),
899            rendering_context,
900            url: None,
901            hidpi_scale_factor: Scale::new(1.0),
902            delegate: Rc::new(DefaultWebViewDelegate),
903            create_new_webview_responder: None,
904            user_content_manager: None,
905            clipboard_delegate: None,
906            #[cfg(feature = "gamepad")]
907            gamepad_delegate: None,
908        }
909    }
910
911    pub(crate) fn new_for_create_request(
912        servo: &Servo,
913        rendering_context: Rc<dyn RenderingContext>,
914        responder: IpcResponder<Option<NewWebViewDetails>>,
915    ) -> Self {
916        let mut builder = Self::new(servo, rendering_context);
917        builder.create_new_webview_responder = Some(responder);
918        builder
919    }
920
921    pub fn delegate(mut self, delegate: Rc<dyn WebViewDelegate>) -> Self {
922        self.delegate = delegate;
923        self
924    }
925
926    pub fn url(mut self, url: Url) -> Self {
927        self.url = Some(url);
928        self
929    }
930
931    pub fn hidpi_scale_factor(
932        mut self,
933        hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
934    ) -> Self {
935        self.hidpi_scale_factor = hidpi_scale_factor;
936        self
937    }
938
939    /// Set the [`UserContentManager`] for the `WebView` being created. The same
940    /// `UserContentManager` can be shared among multiple `WebView`s. Any updates
941    /// to the `UserContentManager` will take effect only after the document is reloaded.
942    pub fn user_content_manager(mut self, user_content_manager: Rc<UserContentManager>) -> Self {
943        self.user_content_manager = Some(user_content_manager);
944        self
945    }
946
947    /// Set the [`ClipboardDelegate`] for the `WebView` being created. The same
948    /// [`ClipboardDelegate`] can be shared among multiple `WebView`s.
949    pub fn clipboard_delegate(mut self, clipboard_delegate: Rc<dyn ClipboardDelegate>) -> Self {
950        self.clipboard_delegate = Some(clipboard_delegate);
951        self
952    }
953
954    /// Set the [`GamepadDelegate`] for the `WebView` being created. The same
955    /// [`GamepadDelegate`] can be shared among multiple `WebView`s.
956    #[cfg(feature = "gamepad")]
957    pub fn gamepad_delegate(mut self, gamepad_delegate: Rc<dyn GamepadDelegate>) -> Self {
958        self.gamepad_delegate = Some(gamepad_delegate);
959        self
960    }
961
962    pub fn build(self) -> WebView {
963        WebView::new(self)
964    }
965}